Hand-rolled mocks made easy

A hybrid approach to writing mock objects

A good mock framework can make writing unit tests a breeze. A bad mock framework can deter even the most conscientious of developers. When writing the tests takes two or three times longer than writing the actual code itself, well, that's just too much work to do everyday. But in an enterprise or open source environment with dozens of constantly changing contributors and an evolving codebase, the importance of unit tests really can't be understated.

This article shows one approach towards simplifying the writing of mock objects. This approach combines the best features of the two types of mocks objects—dynamic and static. Using this technique, a developer can write useful mock objects within minutes.

Background

Mock objects are used to isolate sections of code for testing. Let's say we wanted to test a translation-style object that takes input from a servlet, builds an object, and sends the object to some kind of persister. To test whether the translation is occurring properly, we would use a mock implementation of the persister, pretend to be the servlet, and pass some data into the translation object. The translator accepts the data, none the wiser that it's in a test environment, and passes the result to the mock persistence object. The mock would accept the object, which the test can then inspect and verify.

In the above scenario and in most other unit tests, mock objects allow us to examine and control the test environment. The mock object framework used determines what can be tested and how it can be tested. There are two general types of mock objects: static and dynamic. Static mock objects are prewritten. They usually inherit a common interface, though sometimes they extend the original class. Dynamic mock objects are generated on the fly, usually by some kind of proxy or bytecode manipulation. Static mock objects have the benefit of being specific to the codebase, but they can also be difficult to write. Dynamic mocks are easy to write, but often require much more setup than static mocks, as they offer only the most generic functionality.

The concept

Every mock object fulfills three roles. These roles are independent of each other and are as follows:

  1. The mock must return a predefined value when the proper function is called
  2. The mock must allow the test to see which functions on the mock were called, the arguments passed in, the function return value, and sometimes the order of the calls
  3. The mock must throw a predefined exception if the test demands it

The first requirement can vary wildly depending on how we want to use the mock. The second and third requirements don't change much from mock to mock, which is why we can move the latter two roles into a dynamic framework. In practice, this means writing a static mock that deals only with returning data. We then pass this static mock object through a dynamic system that automatically records function calls and throws predefined exceptions. This allows us to create a full-featured mock object with very little effort.

The difference between this technique and a full-on dynamic approach is that we can write codebase-specific behavior into the static mock object. For instance, if several functions on an object pull from the same pool of data, we can set up our static mock to pull from a data pool that we set up once. On a dynamic object, we would have to set up that same data pool for each function we want to test. To carry this approach even further, it might make sense to hardcode some default values into a mock so that we never need to set up data. As long as the name of the mock reflects this feature and there's no confusion about what's going on, this approach can make unit testing a breeze.

The Java proxy implementation

We can implement this hybrid mock framework using Java proxies: dynamically created interface implementations that send method invocations to a callback handler. By using a proxy, we can record every function call, the arguments passed, and any return values. Also, we can throw exceptions from the proxy, and the calling code will be none the wiser. However, this approach does have one limitation. The proxy objects can only be cast to interfaces. If we want a mock object that is castable to a concrete class, we will have to use the cglib implementation, which I'll discuss later in this article.

To begin the proxy implementation, we need to figure out how to record function calls and how the test will inspect the function calls later. For the sake of code brevity I'm going to use a simple datastructure here:

 public class FunctionCall
{
   public String name = null;
   public Object[] arguments = null;
   public Object returnValue = null;
   public Throwable thrownException = null;
   public Date time = new Date();
}

This FunctionCall object records everything we need to know about a function call.

Now we need to write the class that will intercept function calls and record everything. To do this, we create a custom InvocationHandler. To create a proxy object, we pass in a classloader, an array of interfaces we want to proxy, and our custom InvocationHandler. The returned object can be cast to any of the interfaces we passed in, and whenever a method on the proxy is called, it will automatically invoke the callback on the InvocationHandler. This callback will record the details of the method call and then either throw an exception or call the appropriate method on the static mock object:

 

public class MockController implements InvocationHandler {

private Object originalObject; public ArrayList functionCalls = new ArrayList(); public HashMap exceptions = new HashMap();

public MockController( Object originalObject ) { this.originalObject = originalObject; }

public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable {

FunctionCall functionCall = new FunctionCall();

functionCall.name = method.getName();

functionCall.arguments = args;

if ( exceptions.containsKey( method.getName() ) ) { Throwable throwable = (Throwable) exceptions.get( method.getName() ); functionCall.thrownException = throwable; functionCalls.add( functionCall ); throw throwable; }

Object returnValue = null; try { returnValue = method.invoke( originalObject, args ); } catch ( InvocationTargetException e ) { functionCall.thrownException = e.getTargetException(); functionCalls.add( functionCall ); throw e.getTargetException(); } functionCall.returnValue = returnValue;

functionCalls.add( functionCall );

return returnValue; }

}

The handler object provides two useful items: a detailed history of method calls that the test can then inspect and a way for the test to specify which functions should throw exceptions. These are the two ways to interact with the dynamic pieces of our mock.

The last piece of code, shown below, is the static class used to create the proxy. We could use Java's Proxy class directly, but having our own class hides some of the internal plumbing and provides a layer of abstraction should we ever want to change the implementation.

 

public class MockFactory {

public static Object createMock( Object object, MockController controller ) { return Proxy.newProxyInstance( object.getClass().getClassLoader(), object .getClass().getInterfaces(), controller ); }

}

Now that we have all the pieces, let's unit test what we just wrote:

 

public class MockUnitTest extends TestCase {

public void testCreateProxy() { ArrayList testObject = new ArrayList(); Object mockObject = MockFactory.createMock( testObject, new MockController( testObject ) );

assertNotNull( "MockFactory should have returned a non-null object", mockObject ); assertTrue( "Returned object should be castable to List", mockObject instanceof List ); assertFalse( "Returned object should not be castable to ArrayList", mockObject instanceof ArrayList );

}

public void testFunctionCall() { ArrayList arrayList = new ArrayList(); MockController controller = new MockController( arrayList );

List mockList = (List) MockFactory.createMock( arrayList, controller );

assertEquals( "The controller shows no functions called", 0, controller.functionCalls.size() ); mockList.add( "First Item" );

assertEquals( "The controller shows 1 function call", 1, controller.functionCalls.size() );

FunctionCall call = (FunctionCall) controller.functionCalls.get( 0 ); assertEquals( "We called the 'add' method", "add", call.name ); assertNotNull( "The call had arguments", call.arguments ); assertEquals( "We passed in 1 argument", 1, call.arguments.length ); assertEquals( "The argument we passed in was 'First Item'", "First Item", call.arguments[0] ); assertEquals( "The call returned true", Boolean.TRUE, call.returnValue ); assertNull( "The call didn't throw any exceptions", call.thrownException );

Throwable caught = null; try { mockList.get( 1 ); } catch ( Exception e ) { caught = e; } assertEquals( "The controller shows 2 function calls", 2, controller.functionCalls.size() );

call = (FunctionCall) controller.functionCalls.get( 1 ); assertNotNull( "We caught an exception", caught ); assertEquals( "We recorded the same exception we caught", caught, call.thrownException ); }

public void testThrowException() {

ArrayList arrayList = new ArrayList(); MockController controller = new MockController( arrayList );

List mockList = (List) MockFactory.createMock( arrayList, controller );

mockList.add( "Test" ); mockList.get( 0 );

controller.exceptions.put( "get", new NullPointerException() );

Throwable caught = null; try { mockList.get( 0 ); } catch ( Throwable e ) { caught = e; }

assertTrue( "We caught a NullPointer exception", caught instanceof NullPointerException ); }

}

This test makes sure the dynamic part of the mock framework is working correctly. By using this framework, mock objects are much smaller and easier to write because most of the functionality has been moved to the proxy handler. For example, once this mock object is passed through our MockFactory, it will be able to fulfill all three roles of a mock object:

 

public class DataAccessMockImpl implements DataAccess { public ValueObject valueObject;

public ValueObject load() throws DatabaseException { return valueObject; }

public void save( ValueObject valueObject ) throws DatabaseException { this.valueObject = valueObject; }

}

The cglib implementation

Cglib, short for Code Generation Library, is a library that allows us to dynamically generate Java classes at runtime. It has an easy-to-use interface and allows us to add functionality to existing classes at runtime, instead of compile-time. This comes in handy when we need to cast our generated mock objects to concrete classes instead of interfaces. Cglib dynamically generates subclasses that extend the concrete class we're casting to and, in the process, allow us to insert our custom functionality. The concept is the same, and the code even looks similar. Here is the implementation:

 

public class MockController implements MethodInterceptor {

private Object originalObject; public ArrayList functionCalls = new ArrayList(); public HashMap exceptions = new HashMap();

public MockController( Object originalObject ) { this.originalObject = originalObject; }

public Object intercept( Object proxyObject, Method method, Object[] args, MethodProxy methodProxy ) throws Throwable {

FunctionCall functionCall = new FunctionCall();

functionCall.name = method.getName();

functionCall.arguments = args;

if ( exceptions.containsKey( method.getName() ) ) { Throwable throwable = (Throwable) exceptions.get( method.getName() ); functionCall.thrownException = throwable; functionCalls.add( functionCall ); throw throwable; }

Object returnValue = null; try { returnValue = methodProxy.invoke( originalObject, args ); } catch ( Throwable throwable ) { functionCall.thrownException = throwable; functionCalls.add( functionCall ); throw throwable; }

functionCall.returnValue = returnValue;

functionCalls.add( functionCall );

return returnValue; }

}

Below is the cglib version of our MockFactory class. It looks similar to the MockFactory class for our proxy implementation.

 

public class MockFactory {

public static Object createMock( Object object, MockController controller ) { return Enhancer.create( object.getClass(), object.getClass() .getInterfaces(), controller ); }

}

One warning: While we can cast the proxied object to the mock implementation, do not use the proxy when setting up the mock. Otherwise, we'll end up recording our setup as well, which is rarely desired.

Best practices

Some things to keep in mind while writing mocks using this approach:

1 2 Page 1
Page 1 of 2
How to choose a low-code development platform