How Does JMock work?

12 Sep

JMock is a very clean mocking framework. There are many others out there, but after I read “Growing Object Oriented Software Guided by Tests” I really prefer JMock for the most tasks. We talked about Java’s Proxy class in my last entry. This class – together with reflections – makes it possible to build a project like JMock.

JMock is rather complex. So let’s do our own very small Mocking framework. We won’t cover all JMock features. We ignore things like sequences and states. We won’t even generate nice error messages. But when we understood the basics, it won’t be hard to add them.

Again, there is a project on github which contains the full project. We will talk about the important ideas step by step and create a tiny mocking framework on the way.

Step 1: Defining our Test

We start with the user side of our library. Let’s assume we have an interface (we won’t be able to mock normal classes, see the last posting for an explanation) and some tests that use this interface.

public interface MyInterface {
    void method1();
    int method2();
    void method3(int i);
}
 
public class MockTest {
    @Test
    public void testVoidMock() {
        final MockContext context = new MockContext();
        final MyInterface mock = context.mock(MyInterface.class);
 
        context.defineExpectations(new ExpectationsBuilder() {
            @Override
            void build() {
                oneOf(mock).method1();
                oneOf(mock).method2();
            }
        });
 
        mock.method1();
 
        Assert.assertEquals(0, mock.method2());
 
        boolean cought = false;
        try {
            mock.method1();
        } catch (final AssertionError e) {
            cought = true;
        }
        Assert.assertTrue("Expected exception, bot got none", cought);
 
        context.verify();
    }
 
    @Test
    public void testtMockWithResults() {
        final MockContext context = new MockContext();
        final MyInterface mock = context.mock(MyInterface.class);
 
        context.defineExpectations(new ExpectationsBuilder() {
            @Override
            void build() {
                oneOf(mock).method2();
                willReturn(2);
 
                oneOf(mock).method2();
                willReturn(4);
            }
        });
 
        Assert.assertEquals(2, mock.method2());
        Assert.assertEquals(4, mock.method2());
 
        boolean cought = false;
        try {
            mock.method2();
        } catch (final AssertionError e) {
            cought = true;
        }
        Assert.assertTrue("Expected exception, bot got none", cought);
 
        context.verify();
    }
 
    @Test
    public void testMockWithParameters() {
        final MockContext context = new MockContext();
        final MyInterface mock = context.mock(MyInterface.class);
 
        context.defineExpectations(new ExpectationsBuilder() {
            @Override
            void build() {
                oneOf(mock).method3(2);
                oneOf(mock).method3(5);
            }
        });
 
        mock.method3(2);
 
        boolean cought = false;
        try {
            mock.method3(7);
        } catch (final AssertionError e) {
            cought = true;
        }
        Assert.assertTrue("Expected exception, bot got none", cought);
 
        mock.method3(5);
        context.verify();
    }
 
    @Test(expected = AssertionError.class)
    public void testMockWithTooLessCalls() {
        final MockContext context = new MockContext();
        final MyInterface mock = context.mock(MyInterface.class);
 
        context.defineExpectations(new ExpectationsBuilder() {
            @Override
            void build() {
                oneOf(mock).method1();
                oneOf(mock).method1();
            }
        });
 
        context.verify();
    }
 
    @Test(expected = IllegalStateException.class)
    public void testMockWithException() {
        final MockContext context = new MockContext();
        final MyInterface mock = context.mock(MyInterface.class);
 
        context.defineExpectations(new ExpectationsBuilder() {
            @Override
            void build() {
                oneOf(mock).method1();
                willThrow(new IllegalStateException());
            }
        });
 
        mock.method1();
        context.verify();
    }
}

Let’s go through the tests step by step: The first one defines two expected calls and calls the mock three times. We expect an error and default values as return values. If you want to build your own framework, this is enough for the first steps. We cover a bit more here but I will try to keep it simple.

The second tests adds the feature of “programmed” return values while the third one adds parameters to the expected calls. The fourth test calls the mock only once, but two calls are expected. The last test expects our Framework to simulate an exception.

Take a moment to understand what the tests do – they are the most important part here. What would you expect the framework to do?

Step 2: The two Proxies

One of the trickiest parts is the first test: how do we program our mock? If we take a closer look, we see that there are two situations where the mock object is used. One is the moment when a mock is programed (the part that start’s with “oneOf”). The other is the moment when we call methods on the mock as part of the test.

There are multiple ways to build a mocking framework. One typical works this way:

  • Create a mock
  • Call all expected Methods on the Mock
  • Replay” or „rewind“ the mock
  • Execute the Test
  • Check whether the recorder methods and the invocations in the test were the same

But we’re taking another approach here, as we don’t really call the mock’s methods twice. Instead, we use two proxies. One is used to program the mock; it is returned when we call oneOf. The other one is the real mock object which later checks its invocations. oneOf is defined as

    public <T> T oneOf(T mock)

but it does not simply return mock but an object to program mock. Read the text again and think about it. The trick is essential for the rest of this tutorial. That said we can start our MockContext class.

The Context Class

We come back to the oneOf method in a moment, but the two proxy objects are even created earlier, in the MockContext:

public class MockContext {
    private final Map<Object, MockObjectContext<?>> mocks = new HashMap<>();
 
    public <T> T mock(final Class<T> interfaceClass) {
        return mock(interfaceClass, interfaceClass.getClass().getSimpleName());
    }
 
    public <T> T mock(final Class<T> interfaceClass, final String name) {
        final ExpectationList expectationList = new ExpectationList();
        final T mock = createMock(name, interfaceClass, expectationList);
        final T programmable = createProgrammable(name, interfaceClass,
                expectationList);
 
        mocks.put(mock, new MockObjectContext<T>(name, programmable,
                expectationList));
 
        return mock;
    }
 
    @SuppressWarnings("unchecked")
    private <T> T createMock(final String name, final Class<T> interfaceClass,
            final ExpectationList expectationList) {
        return (T) Proxy.newProxyInstance(getClass().getClassLoader(),
                new Class[] { interfaceClass },
                new MockInvocationHandler(name,
                        expectationList));
    }
 
    @SuppressWarnings("unchecked")
    private <T> T createProgrammable(final String name,
            final Class<T> interfaceClass,
            final ExpectationList expectationList) {
        return (T) Proxy.newProxyInstance(getClass().getClassLoader(),
                new Class[] { interfaceClass },
                new ProgrammableInvocationHandler(name,
                        expectationList));
    }
}

 There are a few new classes here, we will visit them one by one. We keep all mocks saved in a Map. The key is the mock itself. The value is just a container. Every mock object has a name. We can also skip the name and use the name of the interface. The ExpecationList is a list of expected invocations. One proxy fills this list, the other one consumes it. The two proxies are created identically. We cover the rest of MockContext later. Let’s first make sure that the MockObjectContext is really just a container:

public class MockObjectContext<T> {
    private final T programmable;
    private final String name;
    private final ExpectationList expectationList;
 
    public MockObjectContext(final String name,
            final T programmable,
            final ExpectationList expecationList) {
 
        this.name = name;
        this.programmable = programmable;
        this.expectationList = expecationList;
    }
 
    public String getName() {
        return name;
    }
 
    public ExpectationList getExpectationList() {
        return expectationList;
    }
 
    public T getProgrammable() {
        return programmable;
    }
}

 Yep, it is.

The ProgrammableInvocationHandler

The proxies are harder to get. We start with a common baseclass for both proxies, the AbstractInvocationHandler:

public abstract class AbstractInvocationHandler implements InvocationHandler {
    private static final Object[] EMPTY_ARGUMENTS = new Object[0];
    protected final ExpectationList expectationList;
    protected final String name;
 
    public AbstractInvocationHandler(final String name,
            final ExpectationList expectationList) {
        this.name = name;
        this.expectationList = expectationList;
    }
 
    @Override
    public final Object invoke(final Object proxy, final Method method,
            final Object[] argumentsOrNull)
            throws Throwable {
        final Object[] arguments;
 
        if (argumentsOrNull == null) {
            arguments = EMPTY_ARGUMENTS;
        } else {
            arguments = argumentsOrNull;
        }
 
        return invoke(method, arguments);
    }
 
    protected abstract Object invoke(Method method, Object[] arguments)
            throws Throwable;
}

 It stores a few properties and makes sure that our invoke method doesn’t have to handle null values. That’s all. The proxy which is used to program the mock is also a simple one. Here’s the ProgrammableInvocationHandler:

public class ProgrammableInvocationHandler extends AbstractInvocationHandler {
    public ProgrammableInvocationHandler(final String name,
            final ExpectationList expectationList) {
        super(name, expectationList);
    }
 
    @Override
    protected Object invoke(final Method method, final Object[] arguments) {
        expectationList.expectCall(method, arguments);
        return MockUtils.getDefaultValue(method.getReturnType());
    }
}

We forward calls to our expectationList. But what does the return value mean?

Take a look at the tests: we’re actually calling the methods after we use oneOf to receive our objects. So we have to provide a return value! Couldn’t we just return null? Well – yes and no. The result is not important, but if the mocked method has a primitive type as return value, Java throws a NullPointerException since it tries to unbox null. So we write a simple method that returns null for Object, 0 for ints, 0f for floats and so on:

public final class MockUtils {
    private MockUtils() {
    }
 
    public static Object getDefaultValue(final Class<?> type) {
        if (type.equals(Integer.TYPE)) {
            return 0;
        } else if (type.equals(Boolean.TYPE)) {
            return false;
        } else if (type.equals(Long.TYPE)) {
            return 0L;
        } else if (type.equals(Short.TYPE)) {
            return (short) 0;
        } else if (type.equals(Byte.TYPE)) {
            return (byte) 0;
        } else if (type.equals(Float.TYPE)) {
            return 0f;
        } else if (type.equals(Double.TYPE)) {
            return 0.0;
        }
        return null;
    }
}

 Java supports autoboxing, so we don’t have to write Integer.valueOf(0) here.

The MockInvocationHandler

Our mock object is more complex. To save us from exceptions when we work with the object, we define some fallbacks for the methods toString, hashCode and equalsTo. This seems a little bit quick and dirty and leaves much room for improvements. But nothing is harder as a NullPointerException that is raised while some other framework like JUnit tries to build up a helpful error message. This leads us to the following concept:

  • If a call is received check if there is a programmed expectation
  • If there is an expectation, return its programmed value
  • Otherwise, check if it is a call to toString, hashCode or equalsTo and return default values
  • If nothing matches, throw an Exception

The class looks like this (again we assume that some of the logic happens in our AssertionList):

public class MockInvocationHandler extends AbstractInvocationHandler {
    private static final Method toStringMethod;
    private static final Method hashCodeMethod;
    private static final Method equalsMethod;
 
    static {
        try {
            toStringMethod = Object.class.getMethod(
                    "toString", new Class[0]);
            hashCodeMethod = Object.class.getMethod(
                    "hashCode", new Class[0]);
            equalsMethod = Object.class.getMethod(
                    "equals", new Class[] { Object.class });
        } catch (NoSuchMethodException | SecurityException e) {
            throw new RuntimeException(e);
        }
    }
 
    public MockInvocationHandler(final String name,
            final ExpectationList expectationList) {
        super(name, expectationList);
    }
 
    @Override
    public Object invoke(final Method method,
            final Object[] arguments)
            throws Throwable {
        final MockCallResult result = handleMockCall(method, arguments);
        if (result != null) {
            return result.get();
        }
 
        final Object returnValue = handleDefaultMethods(method);
        if (returnValue != null) {
            return returnValue;
        } else {
            throw new AssertionError("Unexpected invocation: "
                    + MockUtils.formatCall(name, method, arguments));
        }
    }
 
    private MockCallResult handleMockCall(final Method method,
            final Object[] arguments) {
        return expectationList.registerCall(method, arguments);
    }
 
    private Object handleDefaultMethods(final Method method) {
        if (method.equals(toStringMethod)) {
            return name + " [mock]";
        } else if(method.equals(equalsMethod)) {
            return false;
        } else if (method.equals(hashCodeMethod)) {
            return this.hashCode();
        } else {
            return null;
        }
    }
}

And this is the formatCall method of MockUtils:

    public static String formatCall(final String name, final Method method,
            final Object[] arguments) {
        final StringBuilder builder = new StringBuilder();
        builder
                .append(name)
                .append(".")
                .append(method.getName())
                .append("(");
 
        for (int i = 0; i < arguments.length; ++i) {
            if (i > 0) {
                builder.append(", ");
            }
            builder.append(arguments[i].toString());
        }
 
        builder.append(")");
        return builder.toString();
    }

Well, this was hard stuff. I suggest again taking a break and re-read the whole stuff. Next we will glue it all together.

Step 3: Defining Expectations

So far we created two proxies per mock. The proxies are connected through a ExpecationList. We will now return to the MockContext class and make the mock programmable.

    public void defineExpectations(final ExpectationsBuilder expectationsBuilder) {
        expectationsBuilder.build(mocks);
    }

We already saw this object in our tests. The class provides a fluent interface to a potential mocking DSL. We provide three methods here: oneOf, willReturn and willThrow:

public abstract class ExpectationsBuilder {
    private Map<Object, MockObjectContext<?>> mocks;
    private MockObjectContext<?> lastMock;
 
    protected final void build(final Map<Object, MockObjectContext<?>> mocks) {
        this.mocks = mocks;
        build();
    }
 
    abstract void build();
 
    protected <T> T oneOf(final T mockObject) {
        @SuppressWarnings("unchecked")
        final MockObjectContext<T> mock = (MockObjectContext<T>) mocks
                .get(mockObject);
 
        if (mock == null) {
            throw new IllegalArgumentException(mockObject + " is not a mock!");
        }

        lastMock = mock;
        return mock.getProgrammable();
    }
 
    protected void willReturn(final Object result) {
        lastMock.getExpectationList().addResultToLastCall(result);
    }
 
    protected void willThrow(final Throwable throwable) {
        lastMock.getExpectationList().addThrowableToLastCall(throwable);
    }
}

willReturn and willThrow are methods that are invoked without any context (this is typical for fluent DSLs), so we have to memorize the mock in the background. This is done by lastMock. willResult and willThrow just forward the calls (the ExpectationList class seems to bet big :-/). oneOf first checks whether the provided object really is a mocked object. If it is one, there’s a MockObjectContext in our map which we ask for the programmable proxy.

I think it’s time that we face the ExpectationList class. We build it in two steps. First, the programming part:

public class ExpectationList {
    private static class Expectation {
        public final Method method;
        public final Object[] arguments;
        public MockCallResult result;
 
        public Expectation(final Method method, final Object[] arguments) {
            this.method = method;
            this.arguments = arguments;
        }
    }
 
    private final List<Expectation> expectations = new ArrayList<>();
    private Expectation lastExpectation;
 
    public void expectCall(final Method method, final Object[] arguments) {
        final Expectation expectation = new Expectation(method, arguments);
        final Object returnValue = MockUtils.getDefaultValue(method.getReturnType());
        expectation.result = new MockCallValueResult(returnValue);
        expectations.add(expectation);
        lastExpectation = expectation;
    }
 
    public void addResultToLastCall(final Object result) {
        lastExpectation.result = new MockCallValueResult(result);
    }
 
    public void addThrowableToLastCall(final Throwable throwable) {
        lastExpectation.result = new MockCallThrowableResult(throwable);
    }
}

expectCall is called by a proxy, while the two add… methods are called from our builder. As there is one list per mock, we can simply store all invoked methods together with their arguments in a list. Note that we also store the result value. We begin by saving the default value of that type (we already used this mechanism in the programmable proxy). So when neither willReturn nor willThrow is called, we just return some kind of 0 or null value. The add… methods overwrite this result with custom values. Let’s see how they look like. These are the three classes:

public interface MockCallResult {
    Object get() throws Throwable;
}
 
public class MockCallThrowableResult implements MockCallResult {
    private final Throwable result;
 
    public MockCallThrowableResult(final Throwable result) {
        this.result = result;
    }
 
    @Override
    public Object get() throws Throwable {
        throw result;
    }
}
 
public class MockCallValueResult implements MockCallResult {
    private final Object result;
 
    public MockCallValueResult(final Object result) {
        this.result = result;
    }
 
    @Override
    public Object get() {
        return result;
    }
}

The get-method is called in MockInvocationHandler#invoke. To be able to see it all in action we need very few more methods.

Step 4: Checking Expectations

There are two tasks here:

  1. Checking whether an invocation is valid
  2. Checking whether all expected invocations happened

In Step 2 we already called a not yet defined method on our ExpectationList; it was called registerCall. Its task is to mark an expectation as “seen” (we just remove it from the list) and provide the result.

    public MockCallResult registerCall(final Method method,
                final Object[] arguments) {
     
        for (final Iterator<Expectation> it = expectations.iterator();
                it.hasNext();) {
            final Expectation expectation = it.next();
                if (expectation.matches(method, arguments)) {
                    it.remove();
                    return expectation.result;
                }
            }
     
            return null;
        }
    }

The Expectation#matches method looks like this:

    public boolean matches(final Method calledMethod,
            final Object[] methodArguments) {
        return calledMethod.equals(method)
                && Arrays.deepEquals(arguments, methodArguments);
    }

The second point is done by checking all ExpectationList objects. If all of them are empty, all expected calls happened. This is done in the context class again:

        public void verify() {
            for (final MockObjectContext<?> entry : mocks.values()) {
                if (!entry.getExpectationList().isEmpty()) {
                    throw new AssertionError(
                            "Not all expected methods were called on "
                                    + entry.getName());
                }
            }
        }

And this is the final snipped in the ExpectationList class:

    public boolean isEmpty() {
        return expectations.isEmpty();
    }

In summary, we did the following:

  • We create two proxy objects per mock
  • The first one fills the list of expectations
  • The second one is the real mock that checks the list of expectations
  • We created a small fluent interface to act with the first mock
  • We verify that every method is called

This stuff is really hard and sometimes confusing, but it 100% pure java with no additional tricks. The next tutorial (it will be very short compared to this one, I promise) shows how to add hamcrest matchers to the library.

One Reply to “How Does JMock work?”

  1. Pingback: How Does JMock work? (2/2) | JoWiSoftware

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert