How to Check a Method was Called on a Mock in Moq
Hi Friends.
We often use mocks in unit tests. They make it easy to isolate the code modules we’re testing. We often set them up to perform functionally: for a given input – whether a specific value, or any value – they return a value to be used elsewhere. In these conditions, we implicitly know the mocked method has been called. Afterall, if its return value is important, its absence would likely cause the test to fail.
However, not every mock needs to return a value. For example, we might be mocking a repository with a void
method that would normally write data to a database. As no return values are involved, the mock likewise wouldn’t (and shouldn’t) return anything.
In this part of the series, we’ll look at how we can check that important methods (like the one previously mentioned) are called when our tests run.
Recreating the Problem and Revisiting a Solution
Let’s assume we have the following code:
public interface IDataRepository
{
public void Save(string data);
}
public class DataService
{
private readonly IDataRepository _repository;
public DataService(IDataRepository repository)
{
_repository = repository;
}
public void SaveData(string data)
{
_repository.Save(data);
}
}
We want to make sure SaveData
calls _repository.Save(data)
. We’ve previously written a similar test where we took the approach of adding a callback to Save
; it added any data passed to it to a List
collection:
[Test]
public void SaveDataCallsSaveOnRepository()
{
// Arrange
var repository = new Mock<IDataRepository>();
var savedData = new List<string>();
repository
.Setup(r => r.Save(It.IsAny<string>()))
.Callback<string>(data => savedData.Add(data));
var service = new DataService(repository.Object);
// Act
service.SaveData("Some data");
// Assert
Assert.That(savedData.Single(), Is.EqualTo("Some data"));
}
This will get the job done, but we need to declare an extra List
variable to store the saved data. If you’d prefer to not introduce new variables, we can do this check another way.
The Batteries Included Approach
Mocks created with Moq keep records of methods that were called, and the arguments passed on each invocation. In the following example, we use a mock’s Verify
method to check that Save
was called with the argument "Some data"
.
[Test]
public void SaveDataCallsSaveOnRepository()
{
// Arrange
var repository = new Mock<IDataRepository>();
var service = new DataService(repository.Object);
// Act
service.SaveData("Some data");
// Assert
repository.Verify(r => r.Save("Some data"));
}
Note that Verify
is on the mock; not the mocked entity – an IDataRepository
in this case. You might remember that using LINQ to Mocks to create mocks returns the mocked entity. In these cases, we can still verify it by getting a reference to the mock with Mock.get()
, as shown in the following example.
[Test]
public void SaveDataCallsSaveOnRepository()
{
// Arrange
var repository = Mock.Of<IDataRepository>();
var service = new DataService(repository);
// Act
service.SaveData("Some data");
// Assert
Mock.Get(repository).Verify(r => r.Save("Some data"));
}
We Don’t Care for Arguments…
Sometimes it’s only important that a mocked method was called. If we aren’t concerned with the arguments passed while doing so, we can use It.IsAny()
– just like setting up a mock.
[Test]
public void SaveDataCallsSaveOnRepository()
{
// Arrange
var repository = new Mock<IDataRepository>();
var service = new DataService(repository.Object);
// Act
service.SaveData("Some data");
// Assert
repository.Verify(r => r.Save(It.IsAny<string>()));
}
…Unless We Do
Let’s imagine we need to call SaveData
twice:
service.SaveData("First save");
service.SaveData("Second save");
When we previously had a callback that added the arguments to a List
, we could verify the data by checking the contents and their order in the List
. While it’s not possible to do this using Verify
, we can inspect the mock’s method invocations. Note the four assertions at the end of the following test:
[Test]
public void SaveDataCallsSaveOnRepository()
{
// Arrange
var repository = new Mock<IDataRepository>();
var service = new DataService(repository.Object);
// Act
service.SaveData("First save");
service.SaveData("Second save");
// Assert
repository.Verify(r => r.Save(It.IsAny<string>()));
Assert.That(repository.Invocations[0].Method.Name,
Is.EqualTo("Save"));
Assert.That(repository.Invocations[0].Arguments[0],
Is.EqualTo("First save"));
Assert.That(repository.Invocations[1].Method.Name,
Is.EqualTo("Save"));
Assert.That(repository.Invocations[1].Arguments[0],
Is.EqualTo("Second save"));
}
We first check that Save
is the name of the first method called on our mock. Then we check that it was called with the argument "First save"
. The next two assertions do the same thing but for the second invocation:
Check that the method invoked was named
Save
, and…It was called with the argument
"Second save"
.
Summary
When using testing mocks, you sometimes need to be sure mocked methods are called. You can do this with a separate collection, or by inspecting the mock.
You can use Verify
to do this quickly and easily, specifying any required arguments at the same time. When a method is called more than once, one disadvantage of this approach is you can’t see the order of the arguments with respect to the method’s successive invocations. If this is important, you can access this information through the mock’s Invocations
property.
Depending on the requirements of your tests, you may find one approach too complex or not complex enough. But by having knowledge of both, you can choose the best one for the task at hand.
Bonus Developer Tip
I find coloured brackets in VS Code extremely helpful. They make working with nested functions simpler because they show which bracket pairs go together. If you miss this feature in Visual Studio, there’s a built-in option to turn it on.
These tips are exclusively for my Substack subscribers and readers, as a thank-you for being part of this journey. Some may be expanded into fuller articles in future, depending on their depth.
But you get to see them here, first.