DEV Community

Cover image for How to Check a Method was Called on a Mock in Moq
Anthony Fung
Anthony Fung

Posted on • Originally published at webdeveloperdiary.substack.com

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!

Top comments (4)

Collapse
 
raddevus profile image
raddevus

This is an very nice article. Thanks for sharing. Now that I've read this one, I'm going to go back and read through your series here on dev.to. Great stuff.
Hey, if you get a chance, I would appreciate it if you read my latest article here on dev.to and comment: Software Developer, Are You Just A Hammer?
Also, I see that youre a fullstack with Angular and C# . That is a very cool combination.

I'm also fullstack using C# for WebAPIs and various other frontends, including iOS, Android and React.

I initially used Angular when it first released but then we moved on to other frontends.

Do you ever use React? Just curious.

Collapse
 
ant_f_dev profile image
Anthony Fung

Thanks for reading raddevus.

Yes, I’ve used React before. Up until ~2016 my tech stack was pretty much limited to C#, Silverlight, and Mongo (and a bit of RabbitMQ). Then (as you know) Silverlight was retired. The company I was at started a new project to rebuild in HTML. So I went from having never really looked at building for Web (other than Silverlight) to being dropped into a project while trying desperately to learn JS, LESS, React/Redux all at once.

After that, I moved to AngularJS – again, about to be dropped – and then transitioned to Angular, which was a less drastic learning curve.

All good fun though 😁

Collapse
 
xaberue profile image
Xavier Abelaira Rueda

Very good article, thanks for sharing, this and the entire series. 👌

Collapse
 
ant_f_dev profile image
Anthony Fung

Thanks for reading, Xavier. Be sure to stay tuned - I've got a few more articles lined up for the series.