Mocking Mocking and Testing Outcomes

Posted by Uncle Bob on 01/23/2010

The number of mocking frameworks has proliferated in recent years. This pleases me because it is a symptom that testing in general, and TDD in particular, have become prevalent enough to support a rich panoply of third-party products.

On the other hand, all frameworks carry a disease with them that I call The Mount Everest Syndrome: “I use it because it’s there.” The more mocking frameworks that appear, the more I see them enthusiastically used. Yet the prolific use of mocking frameworks is a rather serious design smell…

Lately I have seen several books and articles that present TDD through the lens of a mocking framework. If you were a newbie to TDD, these writings might give you the idea that TDD was defined by the use of mocking tools, rather than by the disciplines of TDD.

So when should use use a mocking framework? The answer is the same for any other framework. You use a framework only when that framework will give you a significant advantage.

Why so austere? Why shouldn’t you use frameworks “just because they are there”? Because frameworks always come with a cost. They must be learned by the author, and by all the readers. They become part of the configuration and have to be maintained. They must be tracked from version to version. But perhaps the most significant reason is that once you have a hammer, everything starts to look like a nail. The framework will put you into a constraining mindset that prevents you from seeing other, better solutions.

Consider, for example, this lovely bit of code that I’ve been reviewing recently. It uses the Moq framework to initialize a test double:

var vehicleMock = Mocks.Create<IClientVehicle>()  .WithPersistentKey()  .WithLogicalKey().WithLogicalName()  .WithRentalSessionManager(rsm =>     {       var rs = Mocks.Create<IRentalSession>();       rsm.Setup(o => o.GetCurrentSession()).Returns(rs.Object);       rsm.Setup(o =>        o.GetLogicalKeyOfSessionMember(It.IsAny<string>(),         It.IsAny<int>())).Returns("Rental");     })  .AddVehicleMember<IRoadFactory>()  .AddVehicleMember<IRoadItemFactory>(rf => rf.Setup(t =>      t.CreateItems(It.IsAny<IRoad>())).Returns(pac))  .AddVehicleMember<ILegacyCorporateRental>()  .AddVehicleMember<IRentalStation>(     m => m.Setup(k => k.Facility.FacilityID).Returns(0))  .AddVehicleMember<IRoadManager>(m=>     m.Setup(k=>k.GetRoundedBalanceDue(25,It.IsAny<IRoad>())).Returns(25));

Some of you might think I’m setting up a straw-man. I’m not. I realize that bad code can be written in any language or framework, and that you can’t blame the language or framework for bad code.

The point I am making is that code like this was the way that all unit tests in this application were written. The team was new to TDD, and they got hold of a tool, and perhaps read a book or article, and decided that TDD was done by using a mocking tool. This team is not the first team I’ve seen who have fallen into this trap. In fact, I think that the TDD industry as a whole has fallen into this trap to one degree or another.

Now don’t get me wrong. I like mocking tools. I use them in Ruby, Java, and .Net. I think they provide a convenient way to make test-doubles in situations where more direct means are difficult.

For example, I recently wrote the following unit test in FitNesse using the Mockito framework.

  @Before   public void setUp() {     manager = mock(GSSManager.class);     properties = new Properties();   }    @Test   public void credentialsShouldBeNullIfNoServiceName() throws Exception {     NegotiateAuthenticator authenticator =        new NegotiateAuthenticator(manager, properties);     assertNull(authenticator.getServerCredentials());     verify(manager, never()).createName(       anyString(), (Oid) anyObject(), (Oid) anyObject());   }

The first line in the setUp function is lovely. It’s kind of hard to get prettier than that. Anybody reading it understands that manager will be a mock of the GSSManager class.

It’s not too hard to understand the test itself. Apparently we are happy to have the manager be a dummy object with the constraint that createName is never called by NegotiateAuthenticator. The anyString() and anyObject() calls are pretty self explanatory.

On the other hand, I wish I could have said this:

assertTrue(manager.createNameWasNotCalled());

That statement does not require my poor readers to understand anything about Mockito. Of course it does require me to hand-roll a manager mock. Would that be hard? Let’s try.

First I need to create a dummy.

  private class MockGSSManager extends GSSManager {     public Oid[] getMechs() {       return new Oid[0];     }      public Oid[] getNamesForMech(Oid oid) throws GSSException {       return new Oid[0];     }      public Oid[] getMechsForName(Oid oid) {       return new Oid[0];     }      public GSSName createName(String s, Oid oid) throws GSSException {       return null;     }      public GSSName createName(byte[] bytes, Oid oid) throws GSSException {       return null;     }      public GSSName createName(String s, Oid oid, Oid oid1) throws GSSException {       return null;     }      public GSSName createName(byte[] bytes, Oid oid, Oid oid1) throws GSSException {       return null;     }      public GSSCredential createCredential(int i) throws GSSException {       return null;     }      public GSSCredential createCredential(GSSName gssName, int i, Oid oid, int i1) throws GSSException {       return null;     }      public GSSCredential createCredential(GSSName gssName, int i, Oid[] oids, int i1) throws GSSException {       return null;     }      public GSSContext createContext(GSSName gssName, Oid oid, GSSCredential gssCredential, int i) throws GSSException {       return null;     }      public GSSContext createContext(GSSCredential gssCredential) throws GSSException {       return null;     }      public GSSContext createContext(byte[] bytes) throws GSSException {       return null;     }      public void addProviderAtFront(Provider provider, Oid oid) throws GSSException {     }      public void addProviderAtEnd(Provider provider, Oid oid) throws GSSException {     }   }

“Oh, ick!” you say. Yes, I agree it’s a lot of code. On the other hand, it took me just a single keystroke on my IDE to generate all those dummy methods. (In IntelliJ it was simply command-I to implement all unimplemented methods.) So it wasn’t particularly hard. And, of course, I can put this code somewhere where nobody had to look at it unless they want to. It has the advantage that anybody who knows Java can understand it, and can look right at the methods to see what they are returning. No “special” knowledge of the mocking framework is necessary.

Next, let’s’ make a test double that does precisely what this test needs.

  private class GSSManagerSpy extends MockGSSManager {     public boolean createNameWasCalled;      public GSSName createName(String s, Oid oid) throws GSSException {       createNameWasCalled = true;       return null;     }   }

Well, that just wasn’t that hard. It’s really easy to understand too. Now, let’s rewrite the test.

  @Test   public void credentialsShouldBeNullIfNoServiceNameWithHandRolledMocks() throws Exception {     NegotiateAuthenticator authenticator = new NegotiateAuthenticator(managerSpy, properties);     assertNull(authenticator.getServerCredentials());     assertFalse(managerSpy.createNameWasCalled);   }

Well, that test is just a load easier to read than verify(manager, never()).createName(anyString(), (Oid) anyObject(), (Oid) anyObject());.

“But Uncle Bob!” I hear you say. “That scenario is too simple. What if there were lots of dependencies and things…” I’m glad you asked that question, because the very next test is just such a situation.

  @Test   public void credentialsShouldBeNonNullIfServiceNamePresent() throws Exception {     properties.setProperty("NegotiateAuthenticator.serviceName", "service");     properties.setProperty("NegotiateAuthenticator.serviceNameType", "1.1");     properties.setProperty("NegotiateAuthenticator.mechanism", "1.2");     GSSName gssName = mock(GSSName.class);     GSSCredential gssCredential = mock(GSSCredential.class);     when(manager.createName(anyString(), (Oid) anyObject(), (Oid) anyObject())).thenReturn(gssName);     when(manager.createCredential((GSSName) anyObject(), anyInt(), (Oid) anyObject(), anyInt())).thenReturn(gssCredential);     NegotiateAuthenticator authenticator = new NegotiateAuthenticator(manager, properties);     Oid serviceNameType = authenticator.getServiceNameType();     Oid mechanism = authenticator.getMechanism();     verify(manager).createName("service", serviceNameType, mechanism);     assertEquals("1.1", serviceNameType.toString());     assertEquals("1.2", mechanism.toString());     verify(manager).createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, mechanism, GSSCredential.ACCEPT_ONLY);     assertEquals(gssCredential, authenticator.getServerCredentials());   }

Now I’ve got three test doubles that interact with each other; and I am verifying that the code under test is manipulating them all correctly. I could create hand-rolled test doubles for this; but the wiring between them would be scattered in the various test-double derivatives. I’d also have to write a significant number of accessors to get the values of the arguments to createName and createCredential. In short, the hand-rolled test-double code would be harder to understand than the Mockito code. The Mockito code puts the whole story in one simple test method rather than scattering it hither and yon in a plethora of little derivatives.

What’s more, since it’s clear that I should use a mocking framework for this test, I think I should be consistent and use if for all the tests in this file. So the hand-rolled MockGSSManager and ManagerSpy are history.

“But Uncle Bob, aren’t we always going to have dependencies like that? So aren’t we always going to have to use a mocking framework?”

That, my dear reader, is the real point of this blog. The answer to that salient questions is a profound: “No!

Why did I have to use Mockito for these tests? Because the number of objects in play was large. The module under test (NegotiateAuthenticator) used GSSName, GSSCredential, and GSSManager. In other words the coupling between the module under test and the test itself was high. (I see lightbulbs above some of your heads.) That’s right, boys and girls, we don’t want coupling to be high!

It is the high coupling between modules and tests that creates the need for a mocking framework. This high coupling is also the cause of the dreaded “Fragile Test” problem. How many tests break when you change a module? If the number is high, then the coupling between your modules and tests in high. Therefore, I conclude that those systems that make prolific use of mocking frameworks are likely to suffer from fragile tests.

Of the 277 unit test files in FitNesse, only 11 use Mockito. The reason for small number is two-fold. First, we test outcomes more often than we test mechanisms. That means we test how a small group of classes behaves, rather than testing the dance of method calls between those classes. The second reason is that our test doubles have no middle class. They are either very simple stubs and spies or they are moderately complex fakes.

Testing outcomes is a traditional decoupling technique. The test doesn’t care how the end result is calculated, so long as the end result is correct. There may be a dance of several method calls between a few different objects; but the test is oblivious since it only checks the answer. Therefore the tests are not strongly coupled to the solution and are not fragile.

Keeping middle-class test doubles (i.e. Mocks) to a minimum is another way of decoupling. Mocks, by their very nature, are coupled to mechanisms instead of outcomes. Mocks, or the setup code that builds them, have deep knowledge of the inner workings of several different classes. That knowledge is the very definition of high-coupling.

What is a “moderately complex fake” and why does it help to reduce coupling? One example within FitNesse is MockSocket. (The name of this class is historical. Nowadays it should be called FakeSocket.) This class derives from Socket and implements all its methods either to remember what was sent to the socket, or to allow a user to read some canned data. This is a “fake” because it simulates the behavior of a socket. It is not a mock because it has no coupling to any mechanisms. You don’t ask it whether it succeeded or failed, you ask it to send or recieve a string. This allows our unit tests to test outcomes rather than mechanisms.

The moral of this story is that the point at which you start to really need a mocking framework is the very point at which the coupling between your tests and code is getting too high. There are times when you can’t avoid this coupling, and those are the times when mocking frameworks really pay off. However, you should strive to keep the coupling between your code and tests low enough that you don’t need to use the mocking framework very often.

You do this by testing outcomes instead of mechanisms.

Comments

Leave a response