Mocking without Interfaces

Summary

This page describes a common problem in using jMock–how to mock collaborators where the default constructor prevents mocking. One approach is to extract an interface, but there is an alternative.

Details

public class A {
  private final B b;
  private final C c;
  public A() {
    b = new B();
    c = new C();
  }
  public int x {
    return b.y() + c.z();
  }
}

Let’s say we want to test A.x() in the above code. Our first attempt:

testX() {
  A a = new A();
  assertEquals(3, A.x());
}

It turns out that ‘b’ and ‘c’ aren’t in the states we’d like, so let’s mock them. We’ll add a new constructor:

public class A {
  private final B b;
  private final C c;
  public A() {
    this(new B(), new C());
  }
  A(B b, C c) {
    this.b = b;
    this.c = c;
  }
  public int x() {
    return b.y() + c.z();
  }
}

In our test, we mock out B and C:

testX() {
  Mock b = mock(B.class);
  b.stubs().method("y").will(returnValue(1));

  Mock c = mock(C.class);
  c.stubs().method("z").will(returnValue(2));

  A = new A((B)b.proxy(), (C)c.proxy());
  assertEquals(3, a.x());
}

We encounter a new problem–B and C can’t be mocked because the default constructors do something we don’t want to do in our tests:

public class B {
  private final int state;
  public B() {
    state = calculateState();
  }
  private int calculateState() {
    /* access database or something equally unsuitable */
  }
}

public class C {
  private int state;
  public C() {
    state = calculateState();
  }
  private int calculateState() {
    /* access database or something equally unsuitable */
  }
}

Let’s consider creating interfaces for B and C. What do we name the interfaces? Should we use IB and B; or B, BImpl? Can we justify creating an interface with only a single implementor? Will this lead to every class having an interface just for tests? These aren’t just theoretical questions. These are real debates I’ve experienced with pairs. It turns out that creating an interface for testing is unexpectedly contentious. I’m going to present a move later that does not necessitate creating an interface, but for now, let’s assume that we resolve these issues and produce this:

public interface IB {
  int y();
}

public interface IC {
  int z();
}

public class B implements IB {/*...*/}

public class C implements IC {/*...*/}

public class A {
  private final IB b;
  private final IC c;
  public A() {
    this(new B(), new C());
  }
  A(IB b, IC c) {
    this.b = b;
    this.c = c;
  }
  public int x() {
    return b.y() + c.z();
  }
}

Now we can write this test:

testX() {
  Mock b = mock(IB.class);
  b.stubs().method("y").will(returnValue(1));

  Mock c = mock(IC.class);
  c.stubs().method("z").will(returnValue(2));

  A = new A((IB)b.proxy(), (IC)c.proxy());
  assertEquals(3, a.x());
}

Now if you happen to be in an environment which is mockist-friendly and not averse to creating interfaces, then you’re done. But what happens if you’re pair isn’t convinced that we need to go to the trouble of creating interfaces and argues for this expedient solution:

public class A {
  private final B b;
  private final C c;
  public A() {
    this(new B(), new C());
  }
  A(B b, C c) {
    this.b = b;
    this.c = c;
  }
  public int x() {
    return calculateY() + calculateZ();
  }
  int calculateY() {
    return b.y();
  }
  int calculateZ() {
    return c.z();
  }
}

testX() {
  A = new A(null, null) {
    int calculateX() {
      return 1;
    }
    int calculateY() {
      return 2;
    }
  };
  assertEquals(3, a.x());
}

We could at this point find ourselves in a debate about the pros and cons of this approach–tying the test to the implementation; getting on to the next feature. Can we sidestep this debate by overcoming the need to create an interface? It turns out we can. The issue is that we want to mock B and C, but the default constructors are non-trivial, so they can’t be mocked. Let’s consider changing the default constructors so they become mockable. We’ll move the contents of the original constructors into factory methods:

public class B {
  private int state;
  public B() {}
  public static B create() {
    B result = new B();
    result.state = calculateState();
    return result;
  }
  private int calculateState() {/*...*/}
}

public class C {
  private int state;
  public C() {}
  public static C create() {
    C result = new C();
    result.state = calculateState();
    return result;
  }
  private int calculateState() {/*...*/}
}

public class A {
  private final B b;
  private final C c;
  public A() {
    this(B.create(), C.create());
  }
  A(B b, C c) {
    this.b = b;
    this.c = c;
  }
  public int x() {
    return b.y() + c.z();
  }
}

The above would allow me to write this test:

testX() {
  Mock b = mock(B.class);
  b.stubs().method("y").will(returnValue(1));

  Mock c = mock(C.class);
  c.stubs().method("z").will(returnValue(2));

  A = new A((B)b.proxy(), (C)c.proxy());
  assertEquals(3, a.x());
}

If we found that we needed to test a class that uses class A as a collaborator we could use the same technique to support mocking of class A without an interface:

public class A {
  private B b;
  private C c;

  // for tests, where A is a mocked collaborator
  public A() {}

  // for production code
  public static A create() {
    return new A(B.create(), C.create());
  }

  // for tests, where A is the class under test
  A(B b, C c) {
    this.b = b;
    this.c = c;
  }

  public int x() {
    return b.y() + c.z();
  }
}

Conclusion

To avoid having to create interfaces for mocking in tests, we can reserve default constructors for mocks and use factory methods to instantiate classes in production code.

It's only fair to share...
Share on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn

Leave a Reply