I’ve read Mocks Aren’t Stubs some time ago. However, it was not until recently, after having a discussion on TDD¬†with a colleague, that I realized how strongly coupled a test is to the tested class’ implementation when using mocks.

Ever since I started using mocks, seeing how easy it is, I’ve been doing tests to archieve a high percentage of code coverage. Thinking the TDD way, this is plain wrong.

Tests should, whenever possible, deal with contracts. Oh, of course. You already knew. We all did. But it’s easy to get lost in the way of the mocks. Stubs on the other hand allow you to test the state of the collaborators, as opposed to calls made by your code. Thus decoupling your test from implementation details.

I know I’m not writing rocket science here. But it stroke me today and I felt like sharing it.

Just for completeness’ sake, here’s a sample of both implementations. Imagine the following User Story:

“A user is registered unless the email address already exists”

With Mocks (using EasyMock)

@Test public void userIsRegisteredWhenEmailIsntAlreadyUsed() {
 // setup
 Repository mocked = createMock(Repository.class);
 UserRegistry tested = new UserRegistry(mocked);
 String email = "foo@bar.com";
 User user = new User(email);
 /* we need to know the repository will
    be asked for the email's existence. */
 expect(mocked.emailExists(email)).andReturn(false);
 // and that it'll be told to store the user.
 expect(mocked.store(user));
 replay(mocked);
 // exercise
 tested.registerUser(email);
 // verify
 verify(mocked);
}

With Stubs

// ok, we need to create a stub. This is the ugly part
class RepositoryStub implements Repository {
 Set knownEmails = new HashSet();
 public boolean emailExists(String email) {
  return knownEmails.contains(email);
 }
 public void store(User u) {
  knownEmails.add(u.getEmail);
 }
 public int getRecordCount() {
  return knownEmails.size();
 }
}
@Test public void userIsRegisteredWhenEmailIsntAlreadyUsed() {
 // setup
 String email = "foo@bar.com";
 Repository stubbed = new RepositoryStub();
 UserRegistry tested = new UserRegistry(stubbed);
 /* verify that the email does not
    exist in the repository at setup */
 assertFalse(stubbed.emailExists(email);
 // exercise
 tested.registerUser(new User(email));
 // and now state verification on the repo
 assertTrue(stubbed.emailExists(email);
}

The test class is quite larger, if you count the stub class’ implementation. But I think it’s worth the effort.
Still, one last question remains. Is this truly a Unit Test? If you add a method on Repository, the test will fail (it won’t compile, actually). This breaks the #1 rule for Unit Tests as I see it.

Special thanks to Patricio Gorin for enlightening me.

Links