DEV Community

Cover image for How to Check How Many Times Mocked Methods Are Called in Unit Tests
Anthony Fung
Anthony Fung

Posted on • Originally published at webdeveloperdiary.substack.com

How to Check How Many Times Mocked Methods Are Called in Unit Tests

In last week’s part, we looked at two ways to check that methods on our Moq testing mocks were called as part of our unit and integration tests.

One way was to set up a callback on our mock to add to a collection, e.g. a List. This can make data easier to work with when writing assertions but means introducing additional variables in the test.

The other involved navigating through a mock’s method-invocation records. While this doesn’t require additional variables, test assertions can become quite complex.

This week, we’ll wrap up by looking at one more option.

How many times did it happen?

Let’s imagine we’re building a new feature for an online shopping platform. We want to send repeat customers (those who have made more than one purchase) an email offering them a loyalty reward. We currently have the following code.

public interface IEmailService
{
    void SendEmail(int customerId);
}

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int TotalPurchases { get; set; }
}

public class CustomerRewardsService
{
    private readonly IEmailService _emailService;

    public CustomerRewardsService(IEmailService emailService)
    {
        _emailService = emailService;
    }

    public void SendEmailToRepeatCustomers(IList<Customer> customers)
    {
        var repeatCustomers = customers.Where(c => c.TotalPurchases > 1);

        foreach (var customer in repeatCustomers)
        {
            _emailService.SendEmail(customer.Id);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

We also want to check that the email is only sent to qualifying customers. While we could write the test using either of the previously explored techniques, let’s look at one more alternative.

[Test]
public void EmailIsSentOnlyToRepeatCustomers()
{
    // Arrange

    var customers = new List<Customer>
    {
        new Customer { Id = 0, TotalPurchases = 0, Name = "Alice" },
        new Customer { Id = 1, TotalPurchases = 1, Name = "Bob" },
        new Customer { Id = 2, TotalPurchases = 2, Name = "Charlie" },
        new Customer { Id = 3, TotalPurchases = 3, Name = "Dave" }
    };

    var emailService = Mock.Of<IEmailService>();
    var rewardsService = new CustomerRewardsService(emailService);

    // Act

    rewardsService.SendEmailToRepeatCustomers(customers);

    // Assert

    Mock.Get(emailService).Verify(s =>
        s.SendEmail(It.IsAny<int>()), Times.Exactly(2));
}
Enter fullscreen mode Exit fullscreen mode

In the Arrange section, we’ve created a list of customers. Only Charlie and Dave have made more than one purchase. As such, we want to make sure (only) two emails are sent while processing the list. We do this in the Assert phase by using Times.Exactly(2) when verifying the mock. As we’re using It.IsAny<int>(), we’re only checking that SendEmail is called twice; we don’t pay attention to the customer ID. To ensure the email is only sent to Charlie and Dave (customers 2 and 3 respectively), we can add the following additional verification statements.

Mock.Get(emailService).Verify(s =>
    s.SendEmail(2), Times.Once);

Mock.Get(emailService).Verify(s =>
    s.SendEmail(3), Times.Once);
Enter fullscreen mode Exit fullscreen mode

Here, we’re using Times.Once to verify that exactly one email is sent to each qualifying customer – not only would sending more than one be spamming, but it would also unnecessarily spend our email credits.

We’ve used exact counts in these examples. If our verification requirements were less strict, we also have other options such as:

  • Times.AtLeast

  • Times.Between

  • Times.AtMost

What if it Never Happened?

So far, our test checks for expected behaviour: one email is sent to Charlie, and one to Dave. However, sometimes it’s just as important (if not more) to check that something doesn’t happen. If we want to explicitly check that no emails are sent to Alice or Bob, we can use the Times.Never or Times.Exactly(0) constraints.

Mock.Get(emailService).Verify(s =>
    s.SendEmail(0), Times.Exactly(0));

Mock.Get(emailService).Verify(s =>
    s.SendEmail(1), Times.Never);
Enter fullscreen mode Exit fullscreen mode

Summary

Sometimes you need to know how many times a method has been called on testing mocks. With Moq, you can verify both exact numbers (including 0), and ranges.

While you could have achieved this using the previously explored approaches, each had its own advantages and disadvantages. If neither feels suitable, specifying the number of times a method is called as part of mock verification might be the best way forward.


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 (once per week), plus bonus developer tips too!

Top comments (0)