DualMock – an EasyMock extension

When I have been using easyMock Mock Objects for testing I often find it
helpful to intersperse expectations and test code, for example:

  mock.start();
  control.replay();
  server.handleCommand("START");
  control.validate();

  control.reset();
  mock.shutdown();
  control.replay();
  server.handleCommand("STOP");
  control.validate();

Doing this with easyMock requires me to validate and reset the object
frequently. I was thinking: What is stopping me from just calling
doing this:

  mock.start();
  server.handleCommand("START");
  // call from the object-under-test, mock automatically assumes replay mode.

  mock.shutdown();
   // Call from the test-harness puts mock back in record mode.
  server.handleCommand("STOP");
  // Call from object-under-test, mock assumes replay mode again

  control.validate();
  // Validates the last "STOP" call

Of course, the problem is that the object-under-test (in this case,
the server) and the test-code uses the same mock object instance. This
lead me to the following solution: Make two mock objects – one for the
expectiations and one for the actual code. Thus the name DualMock.
Here is a snippet of test code:

   public void testImplicitSetVoidCallable() {
       DualMockControl control = DualMockControl.createMock(IMethods.class);
       IMethods record = (IMethods)control.getRecordMock();
       IMethods replay = (IMethods)control.getReplayMock();

       record.simpleMethod();
       replay.simpleMethod();

       record.simpleMethod();
       replay.simpleMethod();

       control.verify();
   }

The code is not completely developed yet, as I have only used it marginally myself. Let me know through email or a comment if you’d like the code.

Dual Mock Test

package org.easymock.tests.dualmock;

import junit.framework.AssertionFailedError;
import junit.framework.TestCase;

import org.easymock.internal.MethodCall;
import org.easymock.internal.OrderedBehavior;
import org.easymock.internal.Range;
import org.easymock.internal.Result;
import org.easymock.tests.IMethods;

public class DualMockTest extends TestCase {

    MethodCall lastExpected;

    private class SpyOnOrderedBehavior extends OrderedBehavior {
        public void addExpected(MethodCall methodCall, Result result, Range count) {
            lastExpected = methodCall;
            super.addExpected(methodCall, result, count);
        }
    }

    private DualMockControl control =
        DualMockControl.createMock(IMethods.class, new SpyOnOrderedBehavior());
    private IMethods record = (IMethods)control.getRecordMock();
    private IMethods replay = (IMethods)control.getReplayMock();

    public void testRecordCall() {
        assertNull(lastExpected);
        record.simpleMethod();
        control.setVoidCallable();
        assertNotNull(lastExpected);
        assertEquals(lastExpected.getMethod().getName(), "simpleMethod");
    }

    public void testUnexpectedCall() {
        assertThrows("replay.simpleMethod", AssertionFailedError.class, new Runnable() {
            public void run() { replay.simpleMethod(); }
        });
    }

    public void testReturnValue() {
        record.booleanReturningMethod(5);
        control.setReturnValue(false);

        boolean result = replay.booleanReturningMethod(5);
        assertEquals(false, result);
    }

    public void testMissingCall() {
        record.oneArgumentMethod(true);
        control.setReturnValue("test");

        assertThrows("mock.verify", AssertionFailedError.class, new Runnable() {
            public void run() { control.verify(); }
        });
    }

    public void testImplicitSetVoidCallable() {
        record.simpleMethod();
        replay.simpleMethod();

        record.simpleMethod();
        replay.simpleMethod();

        control.verify();
    }

    public void testTwoMethodCalls() {
        record.simpleMethod();
        record.simpleMethodWithArgument("test");

        replay.simpleMethod();
        replay.simpleMethodWithArgument("test");

        control.verify();
    }

    public void testOneAndAHalf() {
        record.simpleMethod();
        replay.simpleMethod();

        record.simpleMethod();
        assertThrows("mock.verify", AssertionFailedError.class, new Runnable() {
            public void run() { control.verify(); }
        });
    }

    public void testWrongParameter() {
        record.simpleMethodWithArgument("test1");
        assertThrows("wrong parameter", AssertionFailedError.class, new Runnable() {
            public void run() { replay.simpleMethodWithArgument("wrong argument"); }
        });
    }

    public void testThrowing() {
        record.simpleMethod();
        control.setThrowable(new IllegalArgumentException());
        assertThrows("throwing method", IllegalArgumentException.class, new Runnable() {
            public void run() { replay.simpleMethod(); }
        });
    }

    public void testArgumentWidening() {
        byte expected = 12;
        record.byteReturningMethod(1);
        control.setReturnValue(expected);

        assertEquals(expected, replay.byteReturningMethod(1));
    }

    private void assertThrows(String methodCall, Class expectedException, Runnable runnable) {
        try {
            runnable.run();
            fail("Expected " + methodCall + " to throw " + expectedException);
        } catch ( Throwable ex ) {
            assertEquals(methodCall + " exception", expectedException, ex.getClass());
        }
    }

}

Dual Mock Control

package org.easymock.tests.dualmock;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import org.easymock.MockControl;
import org.easymock.internal.IBehavior;
import org.easymock.internal.JavaProxyFactory;
import org.easymock.internal.MethodCall;
import org.easymock.internal.RecordState;

public class DualMockControl {

    private class DualReplayHandler implements InvocationHandler {

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            recordState.replay();
            return behavior.addActual(new MethodCall(method, args)).returnObjectOrThrowException();
        }
    }

    private static final JavaProxyFactory PROXY_FACTORY = new JavaProxyFactory();

    private Object recordMock;

    private Object replayMock;

    private RecordState recordState;

    private final IBehavior behavior;

    public DualMockControl(Class classToMock, IBehavior behavior) {
        this.behavior = behavior;
        this.recordState = new RecordState(behavior);

        this.recordMock = PROXY_FACTORY.createProxy(classToMock, recordState);
        this.replayMock = PROXY_FACTORY.createProxy(classToMock, new DualReplayHandler());
    }

    public static DualMockControl createMock(Class classToMock, IBehavior behavior) {
        return new DualMockControl(classToMock, behavior);
    }

    public Object getRecordMock() {
        return recordMock;
    }

    public Object getReplayMock() {
        return replayMock;
    }

    public void verify() {
        behavior.verify();
    }

    public void setVoidCallable() {
        recordState.setVoidCallable(MockControl.ONE);
    }

    public void setReturnValue(boolean value) {
        recordState.setReturnValue(value, MockControl.ONE);
    }

    public void setReturnValue(long value) {
        recordState.setReturnValue(value, MockControl.ONE);
    }

    public void setReturnValue(char value) {
        recordState.setReturnValue(value, MockControl.ONE);
    }

    public void setReturnValue(double value) {
        recordState.setReturnValue(value, MockControl.ONE);
    }

    public void setReturnValue(Object value) {
        recordState.setReturnValue(value, MockControl.ONE);
    }

    public void setThrowable(Throwable exception) {
        recordState.setThrowable(exception, MockControl.ONE);
    }

}

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.

Print This Post Print This Post
  • Sravan Kumar
    Thanks alot for a nice explanation on Mock. Plz send the entire code stuff and reading materials on dual mock+ easy mock. Look forward hear from you soon.
blog comments powered by Disqus
Creative Commons Attribution 3.0 Unported
This work is licensed under a Creative Commons Attribution 3.0 Unported.