how to mock implementation details that doesn't lead to a fragile test

Viewed 159

I'm reading a book on Unit Testing, below is the quote and the code (not full code since it is easy to understand what the code does)

enter image description here

First, the author shows a mocking that doesn't lead to fragile tests:

[Fact]
public void Successful_purchase() {
   var mock = new Mock<IEmailGateway>();
   var sut = new CustomerController(mock.Object);

   bool isSuccess = sut.Purchase(customerId: 1, productId: 2, quantity: 5);

   Assert.True(isSuccess);
   mock.Verify(x => x.SendReceipt("customer@email.com", "Shampoo", 5), Times.Once);
}

the author says:

The call to the SMTP service is a legitimate reason to do mocking. It doesn’t lead to test fragility because you want to make sure this type of communication stays in place even after refactoring. The use of mocks helps you do exactly that.

Then the author shows another example that leads to fragile tests:

[Fact]
public void Purchase_succeeds_when_enough_inventory() {
   var storeMock = new Mock<IStore>();
   storeMock.Setup(x => x.HasEnoughInventory(Product.Shampoo, 5)).Returns(true);
   var customer = new Customer();

   bool success = customer.Purchase(storeMock.Object, Product.Shampoo, 5);

   Assert.True(success);
   storeMock.Verify(x => x.RemoveInventory(Product.Shampoo, 5), Times.Once);
}

and the author says:

Unlike the communication between CustomerController and the SMTP service, the RemoveInventory() method call from Customer to Store doesn't cross the application boundary: both the caller and the recipient reside inside the application. Also, this method is neither an operation nor a state that helps the client achieve its goals.

I'm a little bit confused here, so we shouldn't test whether RemoveInventory is called? what happen the developer doesn't write the Purchase method correctly, which causes RemoveInventory method not being called on the IStore object, so the purchase will still success but the internal state is corrupted?

1 Answers

The idea is that, instead of checking the call to RemoveInventory, you need to check the final state of the store. Mocks are not appropriated here, unlike in the example with the SMTP Gateway.

The difference is that the communication with the store doesn't cross the application boundary, while the communication with the SMTP does.

Here is an article that describes this topic in more detail: When to mock

Related