Writing tests for simple, standalone Java classes is easy. Just create an instance of your class and run tests against its methods. But testing gets a whole lot more interesting for classes with complex dependencies on other parts of your application. When testing becomes difficult, consider refactoring your code in order to minimize dependencies. But there will always be cases where classes cannot be tested in isolation.
Suppose that a class named Automobile only runs in the context of a class named Engine. In order to test all aspects of the Automobile class, you find that you have to create many different kinds of Engines. Rather than create real Engine objects, which may require a lot of setup logic, you can write a dummy Engine implementation. This dummy implementation is known as a mock object and it provides a simple way to set up fake testing data.
The mock Engine can even include assertions to ensure that instances of Automobile use the Engine correctly. For example, the Engine may verify that Automobile only calls the Engine.startup( ) method one time. This ability to verify that objects use their environment correctly is a key advantage of mock objects when compared to other testing techniques that only check to see if classes react to method calls correctly.
A mock object is a "fake" implementation of a class or interface, almost always written for the specific purpose of supporting unit tests. When writing JDBC unit tests, you might create mock implementations of interfaces such as Connection, ResultSet, and Statement. In the case of Swing code, you might create a mock implementation of the TableModelListener interface. You create mock objects that always return well-known data and your unit tests use these to exercise your application logic without relying on real objects or databases.
Mock objects should be simple, and should not have dependencies on other mock objects or too many other parts of your application. If your mock objects require a lot of complex setup before they are useful, you probably made them too complex and should look for ways to refactor your code. Additionally, mock objects can provide a self-validation mechanism whereby you can set up an expected set of conditions. As soon as an expectation is violated, the mock object fails the current test. Self-validation helps you locate problems as soon as they occur. If you reuse the same mock object in many different tests, this ability to perform some types of self-validation ensures that you do not have to duplicate the same validation logic in all of your tests.
The recipes in this chapter show a few different ways to implement and use the concept of mock objects. In the simplest form, a mock object is a dummy implementation of a Java interface. You implement specific methods that only return data pertinent to your unit tests. We also show how to use Mock Objects, an open source project containing mock implementations of many well-known Java interfaces and abstract classes. Finally, we introduce MockMaker, another open source tool for automatically generating new mock objects.
What Is a Mock Object?There are two widely accepted interpretations of the term mock object:
Recipe 6.3 shows an example of a mock object that supports expectations and self-validation. This is also the approach taken by tools like MockMaker when they generate mock objects for you. We believe that this is a useful approach because it provides a standard, well-understood way to support these features. On the other hand, if your tests do not require sophisticated mock objects, there is nothing wrong with the first definition listed above. In our opinion, you should write mock objects to support your tests, only adding features that you currently need. |