After two article series where I preached against the use of mocks in tests (first one, second one), I thought I would do a post which outlines situations where they are justified. This article is based on my recent Pluralsight course about Pragmatic Unit Testing.
Inner-system vs inter-system communications
The main issue with the use of mocks is that they encourage focusing on collaborations. That, in turn, often leads to coupling your tests to implementation details as those collaborations are usually not part of the SUT’s public API.
But that is not always the case. Sometimes, the way a system communicates with other systems does constitute its API. Let’s see how it is so.
There are two different types of communication that usually happen in enterprise applications. The first one is communication inside the application and the second one is when your application talks to other applications. We can call them inner-system and inter-system communications respectively. The boundary of a single application is usually determined by the boundary of the process hosting it.
Inner-system communications are implementation details. The collaborations your domain classes go through in order to achieve some goal are not part of their public API, they are not observable from the outside world. Aiming at verifying such details is what you need to avoid as it leads to fragile unit tests.
Inter-system communications is a different matter. Unlike collaborations between classes inside your application, the way your system talks to the outside world does comprise the public API of that system as a whole. It’s the contract, the postcondition it must hold at all times.
That distinction grows from the way separate applications evolve together. One of the main principles of such an evolution is maintaining backward compatibility. Regardless of the refactorings you perform inside your system, the communication pattern it uses to talk to external applications should stay in place, so that the external applications can understand it. For example, the messages your app emits on a bus should preserve their structure, the calls it issues to a payment gateway should have the same number and type of parameters, and so on.
That is where collaboration verification style of unit testing can be beneficial because it can be used to solve this exact problem – verify the communication pattern between your system and the external applications. The resulting tests in such situation don’t couple to implementation details because this type of communication is observable from the outside world and thus comprises part of the application’s public API.
Let’s take an example:
public class Order
private List<Product> _products;
private readonly IUser _user;
public Order(IUser user)
_user = user;
public void AddProduct(Product product)
This is a simple Order class which communicates with a user. When the client code adds a new product to the order, the class informs the user that it needs to update the last bought product.
And here’s the test that verifies that:
public void Test()
var mock = new Mock<IUser>();
var order = new Order(mock.Object);
var product = new Product(“M0359”);
mock.Verify(x => x.UpdateLastBoughtProduct(product));
It creates a mock for the IUser interface, supplies that mock to the order instance, and verifies that the UpdateLastBoughtProduct method was called.
This is an example of a fragile test. The communication between Order and User domain classes (the UpdateLastBoughtProduct method call) cannot be observed from the outside of the application and thus is not part of its public API. Verifying this communication, therefore, leads to coupling tests to implementation details which, in turn, cements those details and prevents you from refactoring them.
Here’s another example:
public class OrderService
private readonly IPaymentGateway _gateway;
public OrderService(IPaymentGateway gateway)
_gateway = gateway;
public void Submit(Order order)
public void Test()
var mock = new Mock<IPaymentGateway>();
var order = new Order(100);
var service = new OrderService(mock.Object);
mock.Verify(x => x.ChargePayment(100m));
Here, the payment service is an external application and thus the call your system issues to that application is a side effect that is visible to the outside world. Coupling to that side effects doesn’t result in fragile tests. Unlike the example with User and Order, you do want to make sure this type of communication stays in place even after refactoring your code base, and the use of collaboration verification helps you do that.
Commands vs queries
The calls your system performs to external systems can be categorized further into commands and queries.
Adding a message to a message queue, sending an email, charging money through a payment service are all examples of commands: they change the state of applications called; produce side effects.
An example of a query would be a call to a location service in order to retrieve a ZIP code for a particular address, getting the latest stock quotes from a trading system and so on. In other words, any external call that doesn’t mutate the state of the callee is a query.
The difference between these types of external calls is that queries don’t comprise the API of your system, they are merely a means to get the information required to execute an operation. You still need to substitute them with test doubles because it’s often the only way to work with external dependencies but you shouldn’t verify that your application actually performs those calls.
Only commands constitute the application’s postconditions, and thus only commands should be backward compatible. Queries, on the other hand, can be re-arranged during refactorings or even completely removed, that shouldn’t be considered a bug by your tests.
Test doubles that are used merely to provide data for the SUT are called stubs. Here you can read more on the differences between them in the context of integration testing: Pragmatic integration testing.
When to use mocks: Summary
It’s important to differentiate inner- and inter-system communications. Collaboration verification (and, consequently, the use of mocks) is best when you need to validate communications between your and other applications. It brings damage when you try to employ it for communications inside your application.
- Inner-system communications are implementation details.
- Inter-system communications (the commands part of it) comprise the application’s API.
- Use mocks and stubs only for inter-system communications.
- External communications can be categorized into commands and queries.
- Queries are not part of the application’s API and should not be verified in tests.
If you enjoyed this article, be sure to check out my Pragmatic Unit Testing Pluralsight course as well.