Developer Forums | About Us | Site Map


Useful Lists

Web Host
site hosted by netplex

Online Manuals

Unit testing with mock objects
By Alexander Day Chaffee & William Pietri - 2004-02-18 Page:  1 2 3 4 5 6

Inner class magic

In Listing 6, we used an anonymous inner subclass of AtmGui to override the createTransaction method. Because we only had to override one simple method, this was a concise way to achieve our goal. If we were overriding multiple methods or sharing the AtmGui subclass between many tests, it could pay to create a full (non-anonymous) member class.

We also used an instance variable to store a reference to the mock object. This is the simplest way to share data between the test method and the specialization class. It is acceptable because our test framework is not multithreaded or reentrant. (If it were, we would have to protect ourselves with synchronized blocks.)

Finally, we defined the mock object itself as a private inner class of the test class -- often a convenient approach because it is clearer to put the mock right next to the test code that uses it, and because inner classes have access to the instance variables of their surrounding class.

Better safe than sorry

Because we overrode the factory method to write this test, it turns out that we no longer have any test coverage of the original creation code (which is now inside the base class's factory method). It may be beneficial to add a test that covers this code explicitly. This can be as simple as invoking the base class's factory method and asserting that the returned object is of the correct type. For example:

    AtmGui atm = new AtmGui();
    Transaction t = atm.createTransaction();
    assertTrue(!(t instanceof MockTransaction));

Note that the inverse, assertTrue(t instanceof Transaction) would not suffice, because a MockTransaction is a Transaction as well.

From factory method to abstract factory

At this point, you may be tempted to go one step further and replace the factory method with a full-fledged abstract factory object, as detailed in Design Patterns by Erich Gamma, et al. (see Resources). In fact, many would have started this approach with a factory object, rather than a factory method -- we did, but soon backed away.

Introducing a third object type (role) into the system has some potential disadvantages:

  1. It increases the complexity without a corresponding increase in functionality.

  2. It may force you to change the public interface to the target object. If an abstract factory object must be passed in, then you must add a new public constructor or mutator.

  3. Many languages have conventions attached to the concept of "factory" that may lead you astray. For instance, in the Java language, factories are often implemented as static methods; this is not appropriate in this situation.

Remember, the whole point of this exercise is to make the object easier to test. Often, designing for testability can push the object's API toward a cleaner, more modular state. But it can be taken too far. Test-driven design changes should not pollute the public interface of the original object.

In the ATM example, as far as the production code is concerned, the AtmGui object only ever makes the one type of Transactionobject (the real kind). The test code would like it to produce a different type (a mock). But forcing the public API to accommodate factory objects or abstract factories, just because the test code wants it to, is the wrong design. If production code has no need to instantiate many types of this collaborator, then adding that ability will make the resulting design needlessly hard to understand.

View Unit testing with mock objects Discussion

Page:  1 2 3 4 5 6 Next Page: Resources

First published by IBM developerWorks

Copyright 2004-2021 All rights reserved.
Article copyright and all rights retained by the author.