Test Doubles: creating a Mock

In this series of posts about Test Doubles we’ve looked at the manual and tool-aided creation of a Dummy Object and the use of the Test Stub and the Test Spy. So it’s time to discuss the Mock (Object), which is defined as follows.

A Mock Object is an Object that replaces a real component on which the SUT depends so that the test can verify its indirect outputs.

A Mock behaves like a Spy in that it can return predefined responses and that it pays attention how it was called by the SUT. The difference and added value of a Mock is that it executes behavior verification: the Mock can be preconfigured to verify indirect output unlike a Spy which only records indirect output.
We’ll look at an example of behavior verification: verifying the amount of method calls. If you want, you can clone / download the examples used in this post on github.

The SUT
We want to write unit tests for the reset method of ResetPasswordService and in those tests we want to check our happy flow and error flow. Besides, we want to verify the amount of calls to the send method of EmailService.

public class ResetPasswordService {

  private static final String BODY = "You can reset your password using this link: http://bit.ly/IqT6zt";
  private static final String SUBJECT = "Reset Your Password";
  private static final long RETRY_INTERVAL = 1000;
  private final EmailService emailService;

  public ResetPasswordService(EmailService emailService) {
    this.emailService = emailService;
  }

  public boolean reset(String email) {
    if (isEmailServiceAvailableWithRetry()) {
      emailService.send(email, SUBJECT, BODY);
      return true;
    }
    return false;
  }
  private boolean isEmailServiceAvailableWithRetry() {
    // implementation omitted
  }
}

We’ll use JMock to achieve this, but there are plenty of other mocking frameworks that can do the job.

The Happy Flow
Let’s first test the scenario in which the password can be reset because the email service is available. But we first need to do some set up.

  private static final String VALID_EMAIL = "tester@test.com";
  private static final String SUBJECT = "Reset Your Password";
  private static final String BODY = "You can reset your password using this link: http://bit.ly/IqT6zt";
  private EmailService mockedEmailService;
  private Mockery context;

  @Before
  public void setUp() {
    context = new Mockery();
    mockedEmailService = context.mock(EmailService.class);
  }

Now that we have our mocked EmailService, we are ready to write our test.

 @Test
  public void expect_successful_password_reset() {
    context.checking(new Expectations() {
      {
        // stub response
        allowing(mockedEmailService).isAvailable();
        will(returnValue(Boolean.TRUE));

        // predefine verification
        exactly(1).of(mockedEmailService).send(VALID_EMAIL, SUBJECT, BODY);
      }
    });

    // install SUT
    ResetPasswordService passwordService = new ResetPasswordService(mockedEmailService);

    // verify state of SUT
    Assert.assertTrue(passwordService.reset(VALID_EMAIL));
    // execute predefined verification
    context.assertIsSatisfied();
  }

With exactly(1).of(emailService).send(VALID_EMAIL, SUBJECT, BODY) we configure the Mock to check that send is called exactly once. We call context.assertIsSatisfied() after the SUT is exercised to execute the verification. This is what makes the Mock more complete than the test Spy: it verifies behavior for you.

The Error Flow
Now it’s time for the scenario in which the password cannot be reset because the email service is not available.

 @Test
  public void expect_unsuccessful_password_reset() {
    context.checking(new Expectations() {
      {
        // stub response
        allowing(mockedEmailService).isAvailable();
        will(returnValue(Boolean.FALSE));
        
        // predefine verification
        exactly(0).of(mockedEmailService).send(VALID_EMAIL, SUBJECT, BODY);
      }
    });

    // install SUT
    ResetPasswordService passwordService = new ResetPasswordService(mockedEmailService);

    // verify state of SUT
    Assert.assertFalse(passwordService.reset(VALID_EMAIL));
    // execute predefined verification
    context.assertIsSatisfied();
  }

It isn’t much different from our previous test, but in this case we verify that send is not called at all.

Bonus
If you want to also see an example of a handwritten version of a Mock Object, clone my repository.

The Use Of A Mock Object
A Mock Object should be used when you want to fully stub a real collaborator and verify indirect output. Most of the times a Mock Object can replace a Test Spy. Next up, the Fake Object.

Leave a Reply

Your email address will not be published. Required fields are marked *