Verifying Execution Order in Orchestration Code Tests
About a year ago, I described the distinction between transformation and orchestration code. In short: the former deals with data transformation and the latter with service invocation.
Transformation code can be easily unit-tested: You have input x and expect it to be transformed into output y. Done.
Orchestration code, on the other hand, does not have such an output y (or at least it’s not the main focus of the code). It rather calls different services and moves data between them - it orchestrates the data flow (hence the name).
If you want to unit-test (not integration-test) orchestration code, you’d normally create abstractions for the called services (e.g. interfaces), inject them into the orchestration code, mock these abstractions in your tests and eventually verify that the expected methods on the mocks have been called.
This is a reasonable approach to test orchestration code, yet it leaves one big gap: you don’t know the order in which the mocks have been called.
Let’s look an easy example. We have an ItemDeletionHandler here that loads an item, soft-deletes and then stores it again.
public class ItemDeletionHandler(
IItemDeletionService deletionService,
IItemRepository repository)
{
public async Task HandleAsync(Guid itemId)
{
var item = await repository.GetItemByIdAsync(itemId);
await deletionService.DeleteItemAsync(item);
await repository.StoreItemAsync(item);
}
}This can be unit tested using Moq and AutoFixture. We create mocks for both services, inject them into the SUT, set up the repository mock to return a randomly generated item when called with the correct itemId and in the end verify that the deletion was executed & the item stored again.
Disclaimer: There have been some privacy concerns regarding Moq in the past. Any version prior to v4.20.0 should be safe to use, though.
[Fact]
public async Task HandleAsync_WithItemFound_ShouldCallServices()
{
// Arrange
var deletionServiceMock = new Mock<IItemDeletionService>(MockBehavior.Loose);
var repositoryMock = new Mock<IItemRepository>(MockBehavior.Loose);
var sut = new ItemDeletionHandler(deletionServiceMock.Object, repositoryMock.Object);
var itemId = Guid.NewGuid();
var item = new Fixture().Create<Item>();
repositoryMock.Setup(r => r.GetItemByIdAsync(itemId)).ReturnsAsync(item);
// Act
await sut.HandleAsync(itemId);
// Assert
deletionServiceMock.Verify(ds => ds.DeleteItemAsync(item), Times.Once);
repositoryMock.Verify(r => r.StoreItemAsync(item), Times.Once);
}Running the test is successful. Now, to intentionally break the logic and verify the usefulness of our test, we’ll switch the call order and store the item before we delete it.
public class ItemDeletionHandler(
IItemDeletionService deletionService,
IItemRepository repository)
{
public async Task HandleAsync(Guid itemId)
{
var item = await repository.GetItemByIdAsync(itemId);
await repository.StoreItemAsync(item);
await deletionService.DeleteItemAsync(item);
}
}If we run the test now … it’s still green. Moq does not have a built-in verification of call order. It verifies that (and how) a method/property/… on a mock was called, but not in which order. However, knowing and relying on that order is in a lot of cases paramount - be it in our little item deletion example here, when publishing events or with any other logic operation.
NSubstitute has a built-in feature for this with Received.InOrder. Here we specify the expected calls in the correct order and see how the test fails when the order is wrong.
[Fact]
public async Task HandleAsync_WithItemFound_ShouldCallServices2()
{
// Arrange
var deletionServiceMock = Substitute.For<IItemDeletionService>();
var repositoryMock = Substitute.For<IItemRepository>();
var sut = new ItemDeletionHandler(deletionServiceMock, repositoryMock);
var itemId = Guid.NewGuid();
var item = new Fixture().Create<Item>();
repositoryMock.GetItemByIdAsync(itemId).Returns(item);
// Act
await sut.HandleAsync(itemId);
// Assert
Received.InOrder(() =>
{
deletionServiceMock.DeleteItemAsync(item);
repositoryMock.StoreItemAsync(item);
});
}As already mentioned: Moq does not have this kind of functionality, thus I built a package for it myself: Moq.Contrib.InOrder.
I have to mention here that there was already a package called Moq-Sequences with which you’d technically be able to do the same. However, this package mixes the Act and Assert phase of tests, throwing an exception right in the Act phase when a call was made in the wrong order. This leaves tests looking a bit weird as they only have an Act phase without an Assert phase afterwards.
Moq.Contrib.InOrder replaces the .Setup call with .SetupInOrder and wraps a CallQueue around it. In contrast to NSubstitute, this has to happen in the Arrange phase, before the SUT is called.
[Fact]
public async Task HandleAsync_WithItemFound_ShouldCallServices()
{
// Arrange
var deletionServiceMock = new Mock<IItemDeletionService>(MockBehavior.Loose);
var repositoryMock = new Mock<IItemRepository>(MockBehavior.Loose);
var sut = new ItemDeletionHandler(deletionServiceMock.Object, repositoryMock.Object);
var itemId = Guid.NewGuid();
var item = new Fixture().Create<Item>();
repositoryMock.Setup(r => r.GetItemByIdAsync(itemId)).ReturnsAsync(item);
var queue = CallQueue.Create(_ =>
{
deletionServiceMock.SetupInOrder(ds => ds.DeleteItemAsync(item));
repositoryMock.SetupInOrder(r => r.StoreItemAsync(item));
});
// Act
await sut.HandleAsync(itemId);
// Assert
queue.VerifyOrder();
}For more documentation on multiple calls of the same method, loops and logging, check out the GitHub page.
Following the NSubstitute example, this now, too, verifies the correct order of the method calls whilst maintaining the separation of the Act and Assert phase.