Simplifying Mock Object Testing

From CitconWiki
Revision as of 13:17, 22 August 2008 by Mandersn (Talk | contribs) (rv to Revision as of 06:46, 22 August 2007)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

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.