0

I am attempting to use EasyMock alongside JUnit and have run into difficulties while scheduling method calls on a mocked dependency in a JUnit 4 @Before method.

In the below example the test class MockWithBeforeTest is testing the class ClassUnderTest. Dependency is passed to ClassUnderTest's constructor, in which one of Dependency's methods is called, returning a value needed to initialise ClassUnderTest. This process of initialising ClassUnderTest will be the same for all tests, so I decorate the ClassUnderTest#setUp method with a JUnit 4 @Before annotation.

When testing the method ClassUnderTest#getDerived we expect a call to the mocked Dependency instance to return a value, which we schedule in the method MockWithBeforeTest#testGetDerived. However, this test unexpectedly fails with the error Unexpected method call Dependency.getB() despite the fact that this call is scheduled in MockWithBeforeTest#testGetDerived.

How should I modify the example code such that MockWithBeforeTest#testGetDerived passes?

Example code

import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import org.easymock.EasyMockRule;
import org.easymock.Mock;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

public class MockWithBeforeTest {

  @Rule
  public EasyMockRule rule = new EasyMockRule(this);

  @Mock
  private Dependency dependency;

  private ClassUnderTest classUnderTest;

  @Before
  public void setUp() {
    expect(this.dependency.getA()).andReturn(2);
    replay(this.dependency);

    this.classUnderTest = new ClassUnderTest(this.dependency);
    verify(this.dependency);

  }

  @Test
  public void testGetDerived() {
    expect(this.dependency.getB()).andReturn(3);
    replay(this.dependency);

    assertEquals(6, this.classUnderTest.getDerived(1));
    verify(this.dependency);
  }

}

class ClassUnderTest {
  private int a;
  private Dependency dependency;

  ClassUnderTest(Dependency dependency) {
    this.a = dependency.getA();
    this.dependency = dependency;
  }

  void setA(int val) {
    this.a = val;
  }

  int getDerived(int val) {
    return val * this.a * this.dependency.getB();
  }

}

class Dependency {
  private int a;
  private int b;

  Dependency(int a, int b) {
    this.a = a;
    this.b = b;
  }

  int getA() {
    return this.a;
  }

  int getB() {
    return this.b;
  }

}

Stack Trace

java.lang.AssertionError: 
  Unexpected method call Dependency.getB():
    at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:44)
    at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:101)
    at org.easymock.internal.ClassProxyFactory$MockMethodInterceptor.intercept(ClassProxyFactory.java:97)
    at Dependency$$EnhancerByCGLIB$$6d3a4341.getB(<generated>)
    at MockWithBeforeTest.testGetDerived(MockWithBeforeTest.java:33)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.easymock.internal.EasyMockStatement.evaluate(EasyMockStatement.java:43)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)

Why not create an instance of Dependency to pass to the constructor?

The example given above is representative of a general problem in which the dependency passed to the class under test is much more complex than Dependency.

Speculation about design problem

I am mindful of the fact that details of the class under test's implementation are leaking into the test class via the scheduled methods on the mocked dependency. However I am not experienced enough with mocking frameworks to tell if this is an unavoidable side effect of mocking, or a symptom of a flaw in my design. Any guidance on this would be appreciated.

Software version information

  • Java: 1.8.0_201
  • JUnit: 4.12
  • EasyMock: 4.2
Andrew L
  • 23
  • 7

2 Answers2

1

More research and discussion with colleagues produced a solution. The step I missed is to reset the mocked Dependency object using EasyMock.reset(this.dependency) to allow additional expected calls to be added in the test methods. The fixed MockWithBeforeTest is

public class MockWithBeforeTest {

  @Rule
  public EasyMockRule rule = new EasyMockRule(this);

  @Mock
  private Dependency dependency;

  private ClassUnderTest classUnderTest;

  @Before
  public void setUp() {
    expect(this.dependency.getA()).andReturn(2);
    replay(this.dependency);

    this.classUnderTest = new ClassUnderTest(this.dependency);
    verify(this.dependency);
    reset(this.dependency); // Allow additional expected method calls to be specified
                            // in the test methods

  }

  @Test
  public void testGetDerived() {
    expect(this.dependency.getB()).andReturn(3);
    replay(this.dependency);

    assertEquals(6, this.classUnderTest.getDerived(1));
    verify(this.dependency);
  }

}
Andrew L
  • 23
  • 7
0

The replay() must be called only once, after everything is recorded. That's why it doesn't work here.

Since, in your case you are using the mock in the constructor, you need to instantiate the tested class after the replay.

public class MockWithBeforeTest {

  @Rule
  public EasyMockRule rule = new EasyMockRule(this);

  @Mock
  private Dependency dependency;

  private ClassUnderTest classUnderTest;

  @Before
  public void setUp() {
    expect(this.dependency.getA()).andReturn(2);
  }

  @Test
  public void testGetDerived() {
    expect(this.dependency.getB()).andReturn(3);
    replay(this.dependency);

    this.classUnderTest = new ClassUnderTest(this.dependency);

    assertEquals(6, this.classUnderTest.getDerived(1));

    verify(this.dependency);
  }

}

You could also reset the mock in between but I'm not sure I like that. It will let you pass the constructor and then reuse the mock for the actual test with a new recording.

public class MockWithBeforeTest {

  @Rule
  public EasyMockRule rule = new EasyMockRule(this);

  @Mock
  private Dependency dependency;

  private ClassUnderTest classUnderTest;

  @Before
  public void setUp() {
    expect(this.dependency.getA()).andReturn(2);
    replay(this.dependency);
    this.classUnderTest = new ClassUnderTest(this.dependency);
    reset(this.dependency);
  }

  @Test
  public void testGetDerived() {
    expect(this.dependency.getB()).andReturn(3);
    replay(this.dependency);

    assertEquals(6, this.classUnderTest.getDerived(1));

    verify(this.dependency);
  }

}
Henri
  • 5,551
  • 1
  • 22
  • 29
  • Unfortunately this produces a `java.lang.IllegalStateException: missing behavior definition for the preceding method call: Dependency.getA()` when `Dependency.getA` is called in the `ClassUnderTest` constructor. I believe this is because the constructor is called before the mocked `Dependency` is put into `replay` state in `MockWithBeforeTest.testGetDerived`. However if, as you suggest, the mock cannot be put back into 'record' state after replaying required method calls in the constructor I might be stuck. – Andrew L Mar 30 '20 at 10:46
  • Ah... If you need the mock in the constructor, it's different. You will need to create the tested class in the test. I will update the answer. – Henri Mar 31 '20 at 18:35