Simplifying Mock Object Testing
Facilitated by Tom Adams
Overview
- Tom walked through setting up a unit test using mocks (using JMock 1).
- He demonstrated how hard it can be to read even the most simple interactions due to the amount of scaffolding required
- The group contributed to DRYing up the scaffolding
- A superclass for mock object testing was created to house the simplified scaffolding
- The unit tests became so simple that they appeared to simply be duplicating production code
- Tom's original blog entry on the topic
Tools people mentioned
- JMock 1
- JMock2 wraps “mock” and “controller” in one
- Side effect: need to have some way of explicitly moving from expectation-setting mode and replay mode. JMock2 is closer to the EasyMock style of explicit value seeding and method replay.
- EasyMock
- RMock
- PicoUnit
Summary / comments
- DRY up your mock setup
- Create methods that express intent, e.g. Stack stack = makeMock(Stack.class);
- Put these methods into an abstract testing class (e.g. MockObjecTestCase) for reuse
- Use annotations to further reduce scaffolding and make roles more explicit (auto mocking)
- At some point, the behavioural tests mirror production code and perhaps for non-state-based testing at some point the value is reduced
Process
The example uses jMock 1.2, though the same process has been successfully applied to EasyMock 2.
1. Initial state
Creating a class that requires the use of a stack.
TODO
2. Lose the controller, only deal with the mock
TODO
3. Pull mocks & subject out as fields
TODO
Note. You may need to {{reset()} the mocks between tests if you use a framework such as TestNG that doesn't create a new instance of the test case for each test method (i.e. mocks will share state).
4. Automocking
TODO
5. Use a framework that does all this for you
If all this is too much hard work, you have a couple of options. Push the code out into a class & delegate or push into a super class and (in JUnit) auto-create from .
Even easier, use a framework that does all this for you such as Instinct or Boost.
{{ package com.googlecode.instinct.example.stack;
import static com.googlecode.instinct.expect.Mocker12.expects; import static com.googlecode.instinct.expect.Mocker12.mock; import static com.googlecode.instinct.expect.Mocker12.same; import static com.googlecode.instinct.expect.Mocker12.verify; import com.googlecode.instinct.internal.util.Suggest; import com.googlecode.instinct.marker.annotate.AfterSpecification; import com.googlecode.instinct.marker.annotate.BeforeSpecification; import com.googlecode.instinct.marker.annotate.Dummy; import com.googlecode.instinct.marker.annotate.Context; import com.googlecode.instinct.marker.annotate.Mock; import com.googlecode.instinct.marker.annotate.Specification; import com.googlecode.instinct.marker.annotate.Subject;
@Context public final class MagazinePileContext {
@Dummy private Magazine magazine; @Mock private Stack<Magazine> stack; @Subject private MagazinePile magazinePile;
@BeforeSpecification public void setup() { magazinePile = new MagazinePileImpl(stack); }
@Specification void callsPushOnStackWhenAddAMagazineIsAddedToThePile() { expects(stack).method("push").with(same(magazine)); magazinePile.addToPile(magazine); }
} }}
What next?
- Annotations for defining the role of the test doubles; mocks, dummies, stubs, etc.
- Better framework support for partials, etc.
- Auto-creation of test subject - if constructor takes fields that are mocked out in the test can auto-create the test subject.
- Auto-injection of Spring/Guice/Pico beans.
- Auto-creation of arrays filled with mocks.
- Auto triangulation - Framework takes care of creating (sensible) random values in order to drive out correct code.
- ...?
Downsides
- Lack of explicitness - as you don't have the code in every test, need to go hunting for how things are created.
- Investment in understanding the mocking infrastructure; lifecycle, etc.
- Need to roll your own or use a non-"standard" framework such as Instict.