How to Check a Method was Called on a Mock in Moq

ant_f_dev

Anthony Fung

Posted on August 2, 2023

How to Check a Method was Called on a Mock in Moq

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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"));
}
Enter fullscreen mode Exit fullscreen mode

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"));
}
Enter fullscreen mode Exit fullscreen mode

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"));
}
Enter fullscreen mode Exit fullscreen mode

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>()));
}
Enter fullscreen mode Exit fullscreen mode

…Unless We Do

Let’s imagine we need to call SaveData twice:

service.SaveData("First save");
service.SaveData("Second save");
Enter fullscreen mode Exit fullscreen mode

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"));
}
Enter fullscreen mode Exit fullscreen mode

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.


Thanks for reading!

This article is from my newsletter. If you found it useful, please consider subscribing. You’ll get more articles like this delivered straight to your inbox, plus bonus developer tips too!

💖 💪 🙅 🚩
ant_f_dev
Anthony Fung

Posted on August 2, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related