Snow Speeder
Picture by Olivander
AttributionNon-CommercialShare AlikeSome rights reserved.

Having introduced mock objects, even if you’ve managed to avoid the pitfalls described in the previous posts in this series, you can often end up with some pretty complex and ugly-looking unit tests. There might be a lot of mock objects doing very little or there may be a lot of configuration required for the test. Fight the urge to blame the unit test though. If the unit test is complex, chances are it’s because the underlying code is complex.

For example, you might find yourself creating multiple mock objects simply to reproduce this pattern;

    classA.getB().getC().getD().doSomethingWithD();

In order to mock the invocation of the class D method you end up with something that looks like this if you’re using EasyMock:

    A mockA = createMock(A.class);
    B mockB = createMock(B.class);
    C mockC = createMock(C.class);
    D mockD = createMock(D.class);

    expect(mockA.getB()).andReturn(mockB);
    expect(mockB.getC()).andReturn(mockC);
    expect(mockC.getD()).andReturn(mockD);
    expect(mockD.doSomethingWithD()).andReturn(true);

It gets worse if this pattern repeats itself throughout the code. If it does though it’s probably a code smell and more than likely indicates some potential refactoring of the underlying code. Apply the Remove Middle Man refactoring and go to this:

    classA.doSomething();    // Delegates to doSomethingWithD

The setup for your mock object in your unit test reduces to:

    A mockA = createMock(A.class);
    expect(mockA.doSomething()).andReturn(true);

Refactoring as you go while implementing your tests will help reduce this kind of complexity but be careful! Without a unit test in place for regression testing you may actually introduce bugs without realising it. The general rule for writing unit tests applies: get your test working, refactor the code a little, refactor the test a little and make sure nothing is broken.

Your best bet may be to get a working unit test regardless of how complex it is without any significant refactoring of the code under test and then refactor1. This way you’ll have a test which can be used to do regression testing. Later you can refactor the code being tested and you’ll then be able to refactor the test, simplifying that too.

Having said all that be very aware that you may be overcomplicating your test with your use of mocks and may end up hiding the real test in a pile of mock object expectations. This is where the use of stubs as opposed to mocks can come in handy, especially if refactoring is not an option2. Instead of creating a set of mock objects and setting up their expected method calls, you create stub versions of the collaborator classes which have canned responses to method invocations. You might find that these can then be re-used throughout your tests, simplifying them greatly.

Using my previous example, we might create a stub version of A in the following way:

    public class StubA implements A {
        private B stubB;

        public StubA(B stubB) {
            this.stubB = stubB;
        }

        @Override
        public B getB() {
            return stubB;
        }

        // Other interface methods are implemented as well and
        // can either return dummy values or throw exceptions.
    }

This of course assumes that A is an interface or can be extended with all of it’s public methods being overridden. Don’t forget that the whole point of the exercise is to test code in isolation of the actual implementation of A. We just want our stub implementation to return dummy values or throw exceptions for methods we don’t want (or expect) to be called.

Assuming that the stub versions of B and C look something similar, the setup of our test now looks something like:

    // This is the only mock object - it's the
    // real thing we're looking to happen.
    D mockD = createMock(D.class);
    expect(mockD.doSomethingWithD()).andReturn(true);

    // Here come the stubs which just enable our test.
    A classA = new StubA(new StubB(new StubC(mockD))));

Now when the code under test does this…

    classA.getB().getC().getD().doSomethingWithD();

…our stubs will provide the mock object which will be invoked to do the real work.

The nice thing about this is that because we don’t care about how A, B and C get used we don’t have to create mock objects. On top of that, if these objects are used in the same way in multiple tests then my stub implementations can be re-used making the whole test suite a lot simpler.

I reckon it’s largely a judgement call on when to use mocks or stubs and it’s one that probably only really comes with experience3. In my mind though there’s nothing wrong with starting out with a complex unit test full of mock objects, refactoring the code and then refactoring the test to simplify it all, including replacing mock objects with stubs.

In the next part of this series I’ll show how complexity can also be a result of “engineered bugs”.

– Fintan


1 – Assuming you can get the test working in the first place without refactoring.
2 – The example in this post is based on some real-world code I’ve been looking at recently and is an obvious candidate for refactoring. Your mileage may vary.
3 – You weren’t expecting any easy answers were you?



One Response to “Retrofitting Unit Tests Part 4 – Complex Test, Complex Code”  

  1. My friend on Orkut shared this link with me and I’m not dissapointed that I came to your blog.


Leave a Reply