Saturday, 13 December 2008

Testing Exceptions In JUnit

As of JUnit 4, I'm sure everyone knows that testing exceptions was made a lot easier with the introduction of the @Test annotation that allows us to specify an exception that we expect to be thrown.

public static void throwNullPointer( String msg ) {
throw new NullPointerException( msg );
}

@Test( expected = NullPointerException.class )
public void nullPointerIsThrown() {
throwNullPointer( "Bad Call" );
}

This is much nicer than the old try{} catch{} mechanism that we used to have to implement each time we had to check for an exception.

There is, however, still a limitation to the declarative notation when checking for exceptions. There is no way to test the message. Maybe we don't need to test the messages from exceptions. We generally don't need to if the exception generated by a third party library, the type of the exception should be sufficient in this circumstance.

Another situation I encountered recently where it was necessary to use the try{} catch{} structure for testing exceptions was in an integration test to prove that certain unique constraints existed in a database.

@Test( expected = PersonAlreadyExistsException.class )
public void uniqueConstraintsTest() {
Person firstPerson = new Person( "fred" );
Person secondPerson = new Person( "fred" );
firstPerson.save();
secondPerson.save();
}

In this case it is possible for the Person.save() method to throw a PersonAlreadyExistsException. Looking at the test, we would expect secondPerson.save() to throw an exception. If, before we run this test, there already happens to be a person in the database called fred, which line is going to throw an exception? Not the line we expect. There is a problem with the setup of our test environment, but we don't know it because the test passes anyway, it got the exception it was expecting.

To make sure the exception is thrown exactly where we expect requires us to fall back to the old method of try{} catch{}:

@Test
public void rigorousUniqueConstraintsTest() {
Person firstPerson = new Person( "fred" );
Person secondPerson = new Person( "fred" );
firstPerson.save();
try {
secondPerson.save();
fail( "Expected PersonAlreadyExistsException" );
} catch( PersonAlreadyExistsException ex ) {
assertEquals( "Person with name [fred] already exists", ex.getMessage() );
}
}

Having spent some time working with Groovy I've seen a much nicer replacement for the try{} catch{} structure:

@Test
public void betterRigorousUniqueConstraintTest() {
Person firstPerson = new Person( "fred" );
Person secondPerson = new Person( "fred" );
firstPerson.save();
String message = shouldFail( PersonAlreadyExistsException ) {
secondPerson.save();
}
assertEquals( "Person with name [fred] already exists", message )
}

Here the shouldFail method allows us to specify that the code in the closure should throw a PersonAlreadyExistsException, and we can optionally check the message afterwards if we wish. It would be extremely useful to have this option available when testing in Java, even if the syntax is a bit uglier.

@Test
public void betterRigorousUniqueConstraintTest() {
Person firstPerson = new Person( "fred" );
final Person secondPerson = new Person( "fred" );
firstPerson.save();
String message = shouldFail( PersonAlreadyExistsException.class, new TestCode() {
public void run() {
secondPerson.save();
}
});
assertEquals("Person with name [fred] already exists", message);
}

The implementation for this is fairly simple and once it is done we need write no more try{} catch{} blocks in our test code at the risk of forgetting to put a fail call in correct place.


public class ShouldFail {

private Class expectedException;

public ShouldFail( Class expectedException ) {
this.expectedException = expectedException;
}

protected String test( TestCode testCode ) {
try {
testCode.run();
fail( "Expected " + expectedException.getName() + " but no exception was thrown.");
return null;
} catch( Throwable ex ) {
if( ex.getClass().equals( expectedException ) ) {
return ex.getMessage();
} else {
fail( "Expected " + expectedException.getName() + " but got " + ex.getClass().getName() );
}
}
return null;
}

public static String shouldFail( Class t, TestCode code ) {
return new ShouldFail( t ).test( code );
}
}

In conclusion, I like, and do use the expected property of the @Test annotation in most cases. The use of a shouldFail closure only gives me value when I want to:

  • test the message that comes back on the exception, or
  • there is a chance that the exception we expect can be thrown more than once in the test.

In these cases, I would much rather use a template than implement the exception checking logic each time.

0 comments:

Post a Comment