4.4 Testing Expected Errors

     

4.4 Testing Expected Errors

It is important to test the error-handling behavior of production code in addition to its normal behavior. Such tests generate an error and assert that the error is handled as expected. In other words, an expected error produces a unit test success.

The canonical example of a unit test that checks expected error handling is one that tests whether an expected exception is thrown, as shown in Example 4-8.

Example 4-8. Unit test for expected exception
 LibraryTest.java

   public void  testRemoveNonexistentBook( )  {

      try {

         library.removeBook( "Nonexistent" );

         fail( "Expected exception not thrown" );

      } catch (Exception e) {}

   } 

The expected error behavior is that an exception is thrown when the removeBook( ) method is called for a nonexistent Book . If the exception is thrown, the unit test succeeds. If it is not thrown, fail() is called. The fail( ) method is another useful variation on the basic assert method. It is equivalent to assertTrue(false) , but it reads better.

Since the removeBook( ) method now throws an exception, the testRemoveBook( ) unit test should be updated, as shown in Example 4-9.

Example 4-9. Unit test that fails when an exception is thrown
 LibraryTest.java

   public void  testRemoveBook( )  {

      try {

         library.removeBook( "Dune" );

      } catch (Exception e) {

         fail( e.getMessage( ) );

      }

      Book book = library.getBook( "Dune" );

      assertNull( "book is not removed", book );

   } 

This example uses fail( ) to cause the test to fail when an unexpected exception is thrown. The exception's message attribute is used as the assert message.

The same general pattern is followed to test expected error behavior that is not represented by an exception: the test fails if the error is not seen and succeeds if it is. Example 4-10 shows a unit test that attempts to get a nonexistent Book from the Library and asserts that the expected null Book is returned.

Example 4-10. Unit test checking the expected error getting a nonexistent Book
 LibraryTest.java

   public void  testGetNonexistentBook( )  {

      Book book = library.getBook( "Nonexistent" );

      assertNull( book );

   } 

     

4.5 (Not) Testing Get/Set Methods

Every behavior should be covered by a unit test, but every method doesn't need its own unit test. Many developers don't test get and set methods, because a method that does nothing but get or set an attribute value is so simple that it is considered immune to failure. Tests of such methods are correspondingly trivial, as shown in the test of getTitle( ) in Example 4-11.

Example 4-11. Trivial test of getTitle( ) method
 BookTest.java

   public void  testGetTitle  ( ) {

      Book book = new Book( "Solaris", "Stanislaw Lem" );

      assertEquals( "Solaris",  book.getTitle( )  );

   } 

If a get or set method produces any side effects or otherwise has nontrivial functionality, it should be tested . For example, with lazy initialization, a get method may compute an attribute value before returning it behavior that deserves a unit test.

     

4.6 Testing Protected Behavior

A topic of much discussion within the unit testing community is how to test protected or private methods. Since access to such methods is restricted, writing unit tests for them is not straightforward.

Some developers deal with this quandary by simply ignoring protected or private methods and testing only the public interfaces. It's argued that most of an object's behavior is reflected in its public methods. The behavior of the protected methods can be inferred by the exposed behavior.

There are some drawbacks to this approach. If there are private methods that contain complex functionality, they will not be tested directly. There is a tendency to make everything public so that it is testable. Some behaviors that should be private might be exposed.

It is possible to access and test protected and private methods, depending on the specifics of how a language defines and enforces object access permissions. In C++, making the test class a friend of the production class allows it to access protected interfaces:

 class Library {

#ifdef TEST

   friend class LibraryTest;

#endif

} 

This introduces a reference to the test code into the production code, which is not good. Preprocessor directives such as #ifdef TEST can omit such references when the production code is built.

In Java, a simple technique that allows test classes to access protected and private methods is to declare the methods as package scope and place the test classes in the same package as the production classes. The next section, "Test Code Organization," shows how to arrange Java code this way.

For Java developers who are not satisfied with the direct approach, the Java Reflection API is a tricky way to overcome access protection. The JUnit extension "JUnit-addons" includes a class named PrivateAccessor that uses this approach to access protected or private attributes and methods.

The truly hardcore can follow the examples given here to write their own code that subverts access protection. In Example 4-12, the values of all of Book 's fields are read, regardless of protection. This approach is an ugly hack. Don't read this code just after a meal.

Example 4-12. Example showing use of Reflection API to get private field values
 BookTest.java

import java.lang.reflect.*;



   public void  testGetFields( )  {

      Book book = new Book( "test", "test" );

      Field fields[] = book.getClass( ).  getDeclaredFields( )  ;

      for ( int i = 0; i < fields.length; i++ ) {

         fields[i].  setAccessible( true )  ;

         try {

            String value = (String)  fields[i].get( book )  ;

            assertEquals( "test", value );

         } catch (Exception e) {

            fail( e.getMessage( ) );

         }

      }

   } 

A Book with title and author "test" is created. The Reflection API method getDeclaredFields() returns an array of all of the Book 's fields, and the call to setAccessible( ) allows access to a field. The Reflection API method get( ) is used to obtain each field's value. The test asserts that the value of the field is test .

Similarly, in Example 4-13, all of Book 's get methods are called, ignoring access protection (although the get methods actually are public).

Example 4-13. Example using Reflection API to invoke methods
 BookTest.java

   public void  testInvokeMethods( )  {

      Book book = new Book( "test", "test" );

      Method[] methods = book.getClass( ).  getDeclaredMethods( )  ;

      for ( int i = 0; i < methods.length; i++ ) {

         if ( methods[i].getName( ).startsWith("get") ) {

            methods[i].  setAccessible( true )  ;

            try {

               String value = (String)  methods[i].invoke( book, null )  ;

               assertEquals( "test", value );

            } catch (Exception e) {

               fail( e.getMessage( ) );

            }

         }

      }

   } 

Paralleling the previous example, the Reflection API method getDeclaredMethods() returns all of the Book 's methods, and the call to setAccessible( ) subverts the method's access protection. The test checks the method name and calls only those that have names starting with get to avoid calling Book 's constructor. The Reflection API method invoke() is used to call the methods. Both get methods should return the value test , so this condition is asserted.

Hacks aside, the recommended approach is to design objects so that their important behaviors are public and test those behaviors. Structure the code so that the tests have access to the protected behaviors as well, so that they can be accessed if necessary.