Thursday, March 6, 2014

Stubs, Mocks, Fakes and Dummies

A “mock object”, or “mock”, is a specific kind of test double. It is an object used in a Unit Test which stands in for another object, and carries certain expectations about how what methods will be called, and how they will be called. If the expectations are not met, the test fails. By contrast, other test doubles, such as stubs objects, make no assertions about which methods will be called. The term “Mockist” refers to those programmers who use mock objects in their unit tests. There is another camp of programmers, called “Classicist” by Martin Fowler, who eschew mock objects entirely in their tests.

Dummies

  • passed around but never actually used. Usually they are just used to fill parameter lists

Fakes

  • have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example)

Stubs (classical unit testing)

  • make the SUT believe it's talking with its real collaborators
  • state verification
  • provide canned answers to calls
  • may also record information about calls, such as an email gateway stub that remembers the messages it 'sent', or maybe only how many messages it 'sent'
  • typically, you have to create not just the SUT but also all the collaborators that the SUT needs in response to the test
  • sometimes, alot of work goes into creating test fixtures, but they can be resused
  • cares about the final state - not how that state was derived
  • it's important to only think about what happens from the external interface and to leave all consideration of implementation until after you're done writing the test
  • stubs do not define behavior, just a return value
  • mainly useful when testing query methods that return a result and do not change the observable state of the system (are free of side effects)


Mocks (mockist/behavior driven testing)

  • make the SUT believe it's talking with its real collaborators
  • behavior verification
  • pre-programmed objects with expectations which form a specification of the calls they are expected to receive
  • typically, the SUT and mocks for its immediate collaborator objects
  • less fixture setup required, but mocks must be created every time
  • you are testing the outbound calls of the SUT to ensure it talks properly to its suppliers
  • Mockist tests are thus more coupled to the implementation of a method. Changing the nature of calls to collaborators usually cause a mockist test to break (This can be worsened by the nature of mock toolkits)
  • couples tests to implementation (changing the nature of calls to collaborators usually causes tests to break)
  • Coupling to the implementation interferes with refactoring, since implementation changes are much more likely to break tests than with classic testing
  • writing the test makes you think about the implementation of the behavior
  • must constantly think about how the SUT is going to be implemented in order to write the expectations
  • encourage behavior rich objects
  • Mocks act as a canary in coal mine: they are an early warning system that your code is beginning to depart from the path of small methods, each having a single responsibility, and each interacting with a very small set of collaborators
  • if you aren’t using tests to drive your design, there’s little point to using mock objects
  • Mock objects make more sense in an outside-in model of test-driven development
  • Mock objects enable developers to ferret out the needed interfaces of objects that don’t exist yet
  • The mocks you write while TDDing one layer of the design gives you the clues you need to work out the necessary responsibilities of the next layer down
  • mocks expect a certain method call, but define no return value
  • mainly useful when tesing command methods that change the state of a system but do not return a value


Examples

Stub Example


public interface MailService {
  public void send (Message msg);
}
public class MailServiceStub implements MailService {
  private List messages = new ArrayList();
  public void send (Message msg) {
    messages.add(msg);
  }
  public int numberSent() {
    return messages.size();
  }
}   


We can then use state verification on the stub like this.


class OrderStateTester...
  public void testOrderSendsMailIfUnfilled() {
    Order order = new Order(TALISKER, 51);
    MailServiceStub mailer = new MailServiceStub();
    order.setMailer(mailer);
    order.fill(warehouse);
    assertEquals(1, mailer.numberSent());
  }

Mock Example


class OrderInteractionTester...
  public void testOrderSendsMailIfUnfilled() {
    Order order = new Order(TALISKER, 51);
    Mock warehouse = mock(Warehouse.class);
    Mock mailer = mock(MailService.class);
    order.setMailer((MailService) mailer.proxy());

    mailer.expects(once()).method("send");
    warehouse.expects(once()).method("hasInventory")
      .withAnyArguments()
      .will(returnValue(false));

    order.fill((Warehouse) warehouse.proxy());
  }
}

Example Discussion

In both cases I'm using a test double instead of the real mail service. There is a difference in that the stub uses state verification while the mock uses behavior verification.

In order to use state verification on the stub, I need to make some extra methods on the stub to help with verification. As a result the stub implements MailService but adds extra test methods.

Mock objects always use behavior verification, a stub can go either way. The difference is in how exactly the double runs and verifies the results.

References

http://xunitpatterns.com/
http://martinfowler.com/articles/mocksArentStubs.html
http://confreaks.com/videos/659-rubyconf2011-why-you-don-t-get-mock-objects
http://devblog.avdi.org/2011/09/06/making-a-mockery-of-tdd/
http://jmock.org/oopsla2004.pdf
http://martinfowler.com/bliki/CommandQuerySeparation.html

No comments:

Post a Comment