Annotations in EasyMock 3.2

Lots of people have been asking for annotation support in EasyMock, including me: EASYMOCK-51.

I created EasyMockRule as a way to add annotation driven mocking, and a few other nice features like Hamcrest matcher support for expectation setters and automatic verification, mostly for legacy projects that are using the 2.x version of EasyMock.

Henri at EasyMock recently incorporated annotation support into EasyMock 3.2. The solution is a little different to the approach taken by EasyMockRule, in the way injection is performed and the annotations available. In EasyMockRule I chose to use different annotations for @Mock, @NiceMock and @StrictMock, whereas Henri chose just @Mock with an optional “type” element to specialise where required.

Glad to say though that Henri picked up my suggestion to use @TestSubject to annotate the injection target, which is a much more explicit expression of intent than something like @Autowired.

Injection is done by type, and the whole thing is executed by a JUnit Runner. In the next post I’ll describe how to use the JUnit Rule that I have contributed to EasyMock for version 3.3.

Here’s how to use the annotation support in EasyMock 3.2:

First specify the JUnit Runner like so:

@RunWith(EasyMockRunner.class)
public class SomethingTest {

Then annotate the class fields that will be mocks, and those that will use the mocks – the “test subjects”:


    import static org.easymock.MockType.NICE;

    ...

    @TestSubject
    ThingUser thingUser = new ThingUser();

    @Mock(type = NICE)
    Thing aThing = new Thing();

The @Mock annotation will cause a default mock to be created, or you can use the “type” element to specify a NICE or a STRICT mock.

Now you can use the mocks in your test case by setting expectations, switching into replay mode, and finally verifying:


    @Test
    public void shouldDoSomething() throws Exception {

        expect(aThing.aMethod()).andStubReturn(999);
        replay(aThing);            

        assertThat(thingUser.useThing(), is(999));

        verify(aThing);
    }

Note that you didn’t have to create the mock or set it into the test subject; It’s done automatically.

The runner scans the code for any @mock annotations, and for each one it creates a mock for that field. The mock will be unnamed unless you use the optional “name” element in the annotation like this:


    @Mock(type = NICE, name="aThing")
    Thing aThing = new Thing();

It’s a good idea to name your mocks because the name appears in any expectation failure messages and makes it easier to quickly identify which collaborator is experiencing the problem. (EasyMockRule does this automatically.) For example, without a name, you see error messages like this:


    Expectation failure on verify:
        findAll(): expected: 2, actual: 1

But with a name, you get:


    Expectation failure on verify:
        aThing.findAll(): expected: 2, actual: 1

So now you can see that the field “aThing” was involved. Very useful when you have several mocked collaborators in your test class.

Having created mocks for all the annotated fields, the Runner now proceeds to inject the mocks into any @TestSubject fields, subject to some simple rules.

For each @TestSubject, we will run through all the “@Mock”s and try to inject them to any field of compatible type in the TestSubject.

If the TestSubject field is static or final, we skip it.
If a mock can be assigned to a field, it is injected to that field. The same mock can be assigned more than once, eg to different fields in the same TestSubject or into different TestSubjects.

If two mocks can be assigned to the same field, an error is returned and test execution will fail. This is a consequence of the “injection by type” strategy and a desire to ensure that the behaviour is consistent and predictable. If the second compatible mock was quietly assigned in place of the first than the test outcome could be very confusing and hard to debug. I am currently working on a solution for this that will probably use a “qualifier” element in the annotation to disambiguate this case by specifying the target field name. I’m hoping to have this refinement in 3.3.

So in short it’s quite simple and predictable; Mocks are created automatically, and if a mock can be assigned to a field in a TestSubject, it will be.

You no longer need to call create, but must still call replay and verify.

One way to simplify this is to make your test class extend EasyMockSupport.

When the test class extends EasyMockSupport, then when EasyMockRunner executes the mock creation it does so using the “createMock” method that the test class inherits from EasyMockSupport.

This means that in your test code you can now call replayAll() and verifyAll(). So the earlier example would look something like this:


@RunWith(EasyMockRunner.class)
public class SomethingTest extends EasyMockSupport{

    @Test
    public void shouldDoSomething() throws Exception {

        expect(aThing.aMethod()).andStubReturn(999);
        replayAll();            

        assertThat(thingUser.useThing(), is(999));

        verifyAll();
    }

…which becomes very useful when the number of mocks needed starts to increase.

So there you have it. Annotation driven mocking is available in EasyMock 3.2. Why should you use it? Because those @Mock and @TestSubject annotations are a clear and explicit expression of the role of the fields in the test, and some of the basic engineering code, the creation of mocks and setting into test subjects, which is just noise, disappears from the test code leaving a higher signal-to-noise ratio.

Your tests are easier to write, but more importantly they have a higher signal-to-noise ratio and so they more clearly express the specification of the component being tested.

Advertisements
Posted in EasyMock
One comment on “Annotations in EasyMock 3.2
  1. Keb says:

    Thx, very helpful article. Now i am more tend to use EasyMock instead of other frameworks.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: