In the previous post, I wrote how you can use Spring’s
FactoryBean to facilitate the creation of mock objects for Spring integration tests. Now, it is time to use the EasyMockFactoryBean (in this post EasyMock has been used for creating mock objects, but a similar approach applies to Mockito as well. Start by looking at the MockitoFactoryBean).
Next, imagine that the
Facade class and the
Delegate interface below are part of a bigger system:
Now, we have all the bits and pieces that are needed to start writing the integration test. We start simple to make sure that we autowiring and Spring context has been configured correctly:
The test is executed and it passes without any problem.
A second test is added to the same file with some added mock behavior:
Excellent, that test also passes.
Now that we have warmed up, we add a third test to the test class:
As expected, this test also passes without any problem.
Unfortunately, that is not always true. A closer investigation reveals that all tests pass when they are executed individually. When executing all three tests (either from the IDE or from a build tool like Maven), the third test fails unexpectedly:
java.lang.AssertionError: Unexpected method call Delegate.doSomethingElse(): at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:44) at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:85) at $Proxy8.doSomethingElse(Unknown Source) [...]
This is EasyMock telling us that the mock object is in the wrong state. An EasyMock mock object goes through a series of steps:
- Initialization – Instantiate the mock object.
- Record – Record the expectations of the mock object.
- Replay – Call
replay()on the mock object so that it can replay the recorded state.
- Test – Execute the test assertions.
- Verify – Call
verify()on the mock object to certify that the recorded mock expectations are fulfilled.
In an ordinary unit test, the mock object is thrown away after the verify phase, a new mock instance is created before the next test that can be used to record the new behavior. However, since we are creating a Spring integration test, there is one more thing to consider.
In the testing chapter of the Spring manual it is stated that:
By default, once loaded, the configured ApplicationContext and all of its beans are reused for each test method. Thus the setup cost is incurred only once (per test fixture), and subsequent test execution is much faster.
Consequently, all Spring beans, including mocked beans, will preserve their state between different test methods. The mock object will be in the verify state when the last test begins and not in the record state when
delegateMock.doSomethingElse() is called. To solve the problem, we need some mechanism to ensure that the mock object is in a known, well-defined state before each test.
Both EasyMock and Mockito provide methods to
reset() the state of the mocked objects. Normally, resetting mock objects in the middle of a test method is considered to be a potential code smell, because it is likely that you are testing too much and that the test could be refactored into two separate tests accordingly. Nonetheless, resetting the mock instance @Before each test allow us to keep the same mock instance through the entire lifetime of the test class and yet put it in the record state when before each test starts:
When executing all tests in the test class, we see that that the problem has been solved and all tests pass.
For reference, click the link for the full source code of the EasyMock test class (and the Mockito test class).
You may choose to reset the mocks @After each test, the result will be the same.
reset() method is called between the different test methods (because of the nature of
@Before), so the code smell of calling reset within a test is avoided.
Tests written using Mockito mocks are generally more tolerant for this kind of problem. In contrast to EasyMock, Mockito has no
replay() method and no notion of steps. Therefore, it is possible to add additional mock expectations and to replace existing expectations, both after the test has been executed as well as after the result has been verified. Hence, you should always reset your mock objects before each test so that you do not get any accidental mocking behavior.
As a side note, another solution would be to annotate the test class (or all of its test methods) with the @DirtiesContext annotation. This will effectively recreate the entire application context after each test has been executed, causing all Spring beans to be in their initial state and thus the mock beans to be in the record state. Although this will work, there will be a significant performance penalty compared to the suggested
@After solution, because all beans in the application context will be recreated and re-wired before each test method.
- Spring 3.0.6
- Mockito 1.8.5
- jUnit 4.10
- EasyMock 3.1
- Spring Reference Manual – Test Context Management and Caching
- Spring Reference Manual – SpringJunit4ClassRunner
- Spring Reference Manual – ContextConfiguration
Previous post: Spring Integration Tests, Part I, Creating Mock Objects