views:

481

answers:

5

Hi. I'm pretty new to JUnit, and I don't really know what best practices are for exceptions and exception handling.

For example, let's say I'm writing tests for an IPAddress class. It has a constructor IPAddress(String addr) that will throw an InvalidIPAddressException if addr is null. As far as I can tell from googling around, the test for the null parameter will look like this.

@Test
public void testNullParameter()
{
 try
 {
  IPAddress addr = new IPAddress(null);
  assertTrue(addr.getOctets() == null);
 }
 catch(InvalidIPAddressException e)
 {
  return;
 }

 fail("InvalidIPAddressException not thrown.");
}

In this case, try/catch makes sense because I know the exception is coming.

But now if I want to write testValidIPAddress(), there's a couple of ways to do it:

Way #1:

@Test
public void testValidIPAddress() throws InvalidIPAddressException
{
 IPAddress addr = new IPAddress("127.0.0.1");
 byte[] octets = addr.getOctets();

 assertTrue(octets[0] == 127);
 assertTrue(octets[1] == 0);
 assertTrue(octets[2] == 0);
 assertTrue(octets[3] == 1);
}

Way #2:

@Test
public void testValidIPAddress()
{
 try
 {
  IPAddress addr = new IPAddress("127.0.0.1");
  byte[] octets = addr.getOctets();

  assertTrue(octets[0] == 127);
  assertTrue(octets[1] == 0);
  assertTrue(octets[2] == 0);
  assertTrue(octets[3] == 1);
 }
 catch (InvalidIPAddressException e)
 {
  fail("InvalidIPAddressException: " + e.getMessage());
 }
}

Is is standard practice to throw unexpected exceptions to JUnit or just deal with them yourself?

Thanks for the help.

A: 

For the null test you can simply do this:

public void testNullParameter() {
    try {
            IPAddress addr = new IPAddress(null);
            fail("InvalidIPAddressException not thrown.");
    }
    catch(InvalidIPAddressException e) { }
}

If the exception has a message, you could also check that message in the catch if you wish. E.g.

String actual = e.getMessage();
assertEquals("Null is not a valid IP Address", actual);

For the valid test you don't need to catch the exception. A test will automatically fail if an exception is thrown and not caught. So way #1 would be all you need as it will fail and the stack trace will be available to you anyway for your viewing pleasure.

digiarnie
A: 

if i understand your question, the answer is either - personal preference.

personally i throw my exceptions in tests. in my opinion a test failing by assertion is equivalent to a test failing by an uncaught exception. both show something that needs to be fixed.

the important thing to remember in testing is code coverage.

pstanton
+2  A: 

For tests where I don't expect an exception, I don't bother to catch it. I let JUnit catch the exception (it does this reliably) and don't cater for it at all beyond declaring the throws cause (if required).

I note re. your first example that you're not making use of the @expected annotation viz.

@Test (expected=IndexOutOfBoundsException.class) public void elementAt() {
    int[] intArray = new int[10];

    int i = intArray[20]; // Should throw IndexOutOfBoundsException
  }

I use this for all tests that I'm testing for throwing exceptions. It's briefer than the equivalent catch/fail pattern that I had to use with Junit3.

Brian Agnew
+9  A: 

Actually, the old style of exception testing is to wrap a try block around the code that throws the exception and then add a fail() statement at the end of the try block. Something like this:

public void testNullParameter() {
    try {
        IPAddress addr = new IPAddress(null);
        fail("InvalidIPAddressException not thrown.");
    } catch(InvalidIPAddressException e) {
        assertNotNull(e.getMessage());
    }
}

This isn't much different from what you wrote but:

  1. Your assertTrue(addr.getOctets() == null); is useless.
  2. The intend and the syntax are clearer IMO and thus easier to read.

Still, this is a bit ugly. But this is where JUnit 4 comes to the rescue as exception testing is one of the biggest improvements in JUnit 4. With JUnit 4, you can now write your test like this:

@Test (expected=InvalidIPAddressException.class) 
public void testNullParameter() throws InvalidIPAddressException {
    IPAddress addr = new IPAddress(null);
}

Nice, isn't it?

Now, regarding the real question, if I don't expect an exception to be thrown, I'd definitely go for way #1 (because it's less verbose) and let JUnit handle the exception and fail the test as expected.

Pascal Thivent
I actually tried @Test(expected=InvalidIPAddressException.class) for testNullParameter() and the tests returned errors in eclipse. Have you ever run into that?
Seth
Hmm, I just tried with JUnit 4 and Eclipse and the test just runs fine. However, I forgot the throws clause in my answer which is required if `InvalidIPAddressException` is a checked exception. I've modified my answer accordingly. Does that help?
Pascal Thivent
No. I'll probably post a different question about this though. thanks for the help.
Seth
+1 - Pascal, your answers have been so impressive. Nice work.
duffymo
@duffymo Thank you, your comment is very much appreciated.
Pascal Thivent
The @Test(expected=...) form can actually be worse than an explicit try/catch, for instance if the "setup" part of the test can throw the expected exception. You lose error localization and your test can stay green even if the exercised method stops throwing the correct exception.
Thomas Dufour
A: 

In general way #1 is the way to go, there is no reason to call out a failure over an error - either way the test essentially failed.

The only time way #2 makes sense if you need a good message of what went wrong, and just an exception won't give that to you. Then catching and failing can make sense to better announce the reason of the failure.

Yishai