What are Mock Objects used for?
Here is an example of a simple java class that is difficult if not impossible to test fully without a mock object.Background...
"Joel on Software" article on error reportingWhen reading what Joel said about error reporting, I decided it would be good to see how hard it would be to add the "Source" of an error or other message to a Java application. This lead to the design of a class named "MessageSource". MessageSource has the following properties.
For the sake of simplicity, I'm not including the Application Version but I will say this is pretty easy to do if you look up the Sun java product versioning specification. Check out the section on package versioning in this article.
Solution???
Read sun's javadoc api documentation on the java.lang.Throwable class. Looks like the getStackTrace() method (available since version 1.4 of Java2) is just the trick we need to get the class name, method name and line number at runtime. A StackTraceElement array is returned by the getStackTrace() method.
This is where things get interesting...
Quoted directly from the javadoc "Some virtual machines may, under some circumstances, omit one or more stack frames from the stack trace. In the extreme case, a virtual machine that has no stack trace information concerning this throwable is permitted to return a zero- length array from this method."
So, looks like we have to write code that handles a zero-length array with the knowledge that our java runtime environment is highly unlikely to encounter such an extreme case.
Mock objects to the rescue
My solution will be presented in two parts, first the simplest solution that doesn't cater for the case where a zero-length array is returned, second, the complete solution including a mock object that allows us to mock what the runtime will do in the extreme case where the virtual machine returns a zero-length array.
Junit in action - sample chapter 7First (simplistic) solution...
public class MessageSource
{
private StackTraceElement[] trace;
public MessageSource(Throwable marker)
{
trace = marker.getStackTrace();
}
public String getClassName()
{
return trace[0].getClassName();
}
public String getMethodName()
{
return trace[0].getMethodName();
}
public int getLineNumber()
{
return trace[0].getLineNumber();
}
}
Here is the corresponding unit test...
public class MessageSourceTest extends TestCase
{
public void testExpectedUsage()
{
Throwable marker = new Throwable();
StackTraceElement[] trace = marker.getStackTrace();
int line = trace[0].getLineNumber();
MessageSource src = new MessageSource(marker);
assertEquals("MessageSourceTest", src.getClassName());
assertEquals("testExpectedUsage", src.getMethodName());
assertEquals(line, src.getLineNumber());
}
}
Second complete solution.
Two new classes and one interface are needed.
| StackTraceProvider | Interface |
| DefaultStackTraceProvider | Class |
| MockStackTraceProvider | Class |
public interface StackTraceProvider
{
public StackTraceElement[] getStackTrace(Throwable marker); }
public class DefaultStackTraceProvider implements StackTraceProvider {
public StackTraceElement[] getStackTrace(Throwable marker)
{
return marker.getStackTrace();
}
}
public class MockStackTraceProvider implements StackTraceProvider {
private String type;
public MockStackTraceProvider(String type)
{
this.type = type;
}
public StackTraceElement[] getStackTrace(Throwable marker)
{
if ("zero".equals(type))
{
return new StackTraceElement[0];
} else if ("one".equals(type))
{
return new StackTraceElement[1];
}
{
return null;
}
}
}
Here is the updated MessageSource. It uses the StackTraceProvider interface and by default uses the DefaultStackTraceProvider.
public class MessageSource
{
private StackTraceProvider provider = new DefaultStackTraceProvider();
public static final String UNKNOWN = "unknown";
private Throwable marker;
private StackTraceElement[] trace;
public MessageSource(Throwable marker)
{
this.marker = marker;
trace = provider.getStackTrace(marker);
}
void setProvider(StackTraceProvider provider)
{
this.provider = provider;
trace = provider.getStackTrace(marker);
}
public String getClassName()
{
if (haveTraceElement())
{
return trace[0].getClassName();
} else
{
return UNKNOWN;
}
}
public String getMethodName()
{
if (haveTraceElement())
{
return trace[0].getMethodName();
} else
{
return UNKNOWN;
}
}
public int getLineNumber()
{
if (haveTraceElement())
{
return trace[0].getLineNumber();
} else
{
return -1;
}
}
private boolean haveTraceElement()
{
if ((trace != null)
&& (trace.length > 0)
&& (trace[0] != null))
{
return true;
}
return false;
}
}
Here is the updated unit test, the MockStackTraceProvider is used in three of the four tests to provide the MessageSource class with the same results as when the virtual machine encounters the extreme case referred to in the javadoc.
public class MessageSourceTest extends TestCase
{
public void testExpectedUsage()
{
Throwable marker = new Throwable();
StackTraceElement[] trace = marker.getStackTrace();
int line = trace[0].getLineNumber();
MessageSource src = new MessageSource(marker);
assertEquals("MessageSourceTest", src.getClassName());
assertEquals("testExpectedUsage", src.getMethodName());
assertEquals(line, src.getLineNumber());
}
public void testEmptyStackTraceArray()
{
runTestWithMockProvider("zero");
runTestWithMockProvider("one");
runTestWithMockProvider("null");
}
private void runTestWithMockProvider(String mockType)
{
MessageSource src = new MessageSource(new Throwable());
src.setProvider(new MockStackTraceProvider(mockType));
assertEquals(MessageSource.UNKNOWN, src.getClassName());
assertEquals(MessageSource.UNKNOWN, src.getMethodName());
assertEquals(-1, src.getLineNumber());
}
}
Summary
Mock objects provide significant advantages in ensuring your code is fully tested.
I particularly like this example because it is simple and it illustrates handling an extreme case that would be impossible to test without mock objects. I encourage you to read the entire sample article from the Junit In Action book or better yet, buy your own copy. You will also learn how to use this technique to write "trojan horse" type of mock objects to let you prove that your code does not have resource leaks. The mock technique shown here is also useful for testing how your code handles exceptions that are difficult or impossible to reproduce.
HomeCopyright (c) 2006. Tony Obermeit. All rights reserved worldwide.