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