March 11, 2005 - Using Mock Objects Effectively

As the first field engineer for Agitar, I have been deploying Agitar solutions for over almost two years now on many projects. A lot of people have similar questions about when and how to use mock objects. To make the most of Agitator, mock objects should fulfill two objectives: 1) help make tests portable and easy to run, and 2) help create assertions.

In this blog entry, I'll demonstrate 3 common uses of mock objects that illustrate how to use mock objects to make running and testing code easier.

A common misconception about unit testing is that the class being tested must be isolated and that everything else must be mocked out. This is clearly not a productive use of anyone's time. Unit testing is about verifying the behavior of a method body. What is isolated is the scope of what is being verified. There is no constraint imposed in terms of code execution. In general, real classes ought to be invoked as much as is practical.

If real classes are supposed to be used, then why use mock anything out in the first place. In practice certain services make code execution and code verification difficult.

Consider the case where an application depends on log4j. Log4j has a notoriuosly brittle initialization process. Moreover, a common pattern for initializing loggers is to use static initializers. When these initializers fail under test conditions, the class invoking the logger cannot be loaded in the class loader, and therefore cannot be tested. For these reasons, using a mock of the log4j logger makes it easier to run the code under test conditions. Overcoming the configuration issues can be extremly time consuming and not worthwhile.
Next, let's pretend that the log4j logger is configured for use under test conditions.
The questions that beg to be asked is whether the log entries will be tested. Some people will answer yes, most will answer no. For those that answer no, then clearly it does not matter whether a mock logger is used or not. If the JVM can't tell the difference, then why should we. The best choice then is to use the easiest approach. If the real service (logger in this example) works fast and makes the tests portable, then use it. Otherwise mock it out so that the tests are portable.
For those that will want to test that the application is logging the correct message under the right conditions, then the obvious follow up question is how do you test the log content. Assuming that we are using a file logger, one could open up a file, parse it, try to find the right message (what if multiple threads are all logging to the same resource), and verify that the log contains the correct entry. Basically, this is not an elegant way to test that the application logged the correct message under the correct conditions.
On closer inspection, it becomes clear that the application is not actually logging anything. Log4J is responsible for doing that. What the application is truly doing is sending a signal (a request) to log4j to log a message. That distinction is *very* important. the appropriate test then is simply to verify that the application is sending the correct signal to the serivce provider under the right conditions. The log4j API does not provide methods to read the content of a log. A mock logger could. In this case, even if the real service provider did not compromise the portability of the test, a mock should be used to provide the tranparency needed to test the interaction between the application and the service provider.

I have mentioned portability of test several times. I will expand on that in my next blog entry.

Log4j is a widely used API. It works great. I am using it as an example in this blog simply because I want to relate to as many readers as possible. There are many other services I could have used instead, such as databases, caching services, configuration managers, workflow frameworks, etc. The same circumstances apply to these services as well.
For example, consider a database based application. The application itself is not managing the actual store. It sends signals to the jdbc driver to store and retrieve objects from the database. The jdbc drivers provide no API to spy on the query sent to the driver by the application. When the developer is actively writing code, an assumption is made that the jdbc driver works. So if the developer can verify that the correct signal was sent to the driver, then that developer can assume the database knows how to store or retrieve the object correctly. Later, this assumption will be verified by scenario, functional, and integration testing. A mock database can provide the transparency needed to spy on queries sent to the database. Without a mock database, testing is not portable. I will address this issue in a later blog entry.

Applications interact with service layers in 3 basic ways: 1) they ask for a service, 2) they control/configure the service provider, and 3) they retrieve objects from the service provider. I would like to share below 3 types of methods I use in my mocks over and over againg.

Many of my customers develop struts based applications that are database intensive. The application is actually interacting with the database through a caching layer for performance. In this example we will consider mocking out a cache manager. The cache manager will have the ability to return objects, store objects, and refresh objects. Here is how the mock might be implemented.

public class MockCacheManager implements CacheManager{
	public StoredObject getObjectById(Object id) {
		StoredObject obj = new MockStoredObject( id );
		return obj;
	}

private int numObjectsStored = 0;
private Object storedObject = null;
public void store(Object obj) {
storedObject = obj;
numObjectsStored++;
}

private boolean objectRefreshed = false;
public void refreshObjectById(Object id) {
objectRefreshed = true;
}
private boolean checkRefresh() {
boolean result = objectRefreshed;
objectRefreshed = false;
return result;
}
}

In the first method getObjectById(), the mock can use any means necessary to return objects. Typically, the methods asking for objects do not have a priori knowledge of what should be returned. They simply must react correctly to nulls, empty, or populated data sets.

In the second method store(), the mock provides transparency needed to verify that the application is storing the correct object under the right conditions. Agitar provides a testing framework that ignores access modifiers. So the private numObjectsStored and storedObject fields can be used in assertions to verify that something was stored, and that the right thing was stored under the right conditions, as needed.

In the last method, the application is sending the service provider a signal to change its state without returning data. A boolean can be used as a flag to tell if the event occurred. The problem though is that the flag must be reset to ensure that the right event is dedected. So instead of asserting on the private field, a boolean method is used to return the state of the flag and reset the flag for the next event.

In this way mocks can contain boolean valued functions that can really enhance testing. Complex, multiline (or loop) assertions can be enclosed in single methods that can be easily called from assertions.

My intent in sharing this blog is to relate what has worked for me in many dozens of projects over the last 2 years. I hope I helped take the uncertainty and myth out of using mock objects. Mock objects can be a powerful test aide. But like all tools, they are not always the best tool for the job. In later blog entries I will discuss cases when mocks are not the answer.


Posted by Patrick Smith at March 11, 2005 04:07 PM


Trackback Pings

TrackBack URL for this entry:
http://www.developertesting.com/mt/mt-tb.cgi/151


Comments

"The log4j API does not provide methods to read the content of a log."?

See the new receiver framework - LogFilePatternReceiver will parse log files and turn them into events (it's used in the new version of Chainsaw).

http://cvs.apache.org/viewcvs.cgi/logging-log4j/src/java/org/apache/log4j/varia/LogFilePatternReceiver.java?rev=1.24&view=markup

Posted by: scott on March 11, 2005 09:48 PM

Thank you Scott for pointing out the new log4j receiver framework API. This may indeed make mocking out log4j not necessary in some cases.

Posted by: Patrick on March 14, 2005 08:07 AM

This one article answers many of my queations as to hoew to unit test JDBC Applications.

Posted by: kanthraj on June 26, 2005 09:03 AM

Post a comment




Remember Me?