How to Use Strict Mocking in Moq to Automatically Verify All Setups

ant_f_dev

Anthony Fung

Posted on August 23, 2023

How to Use Strict Mocking in Moq to Automatically Verify All Setups

When using mocks, we often set them up to return values for use in the logic we’re testing. In situations where we have many methods to configure, it’s easy to accidentally forget a setup or two. When this happens, our tests can produce unexpected results and fail. To help narrow down where things go wrong, we can verify the mocks to check that the methods we’ve set up have been called. In previous parts of this series, we’ve seen how this is helpful. However, it can also mean the number of verify statements equals that of the methods set up. In more complex test, we could end up with a lot of code.

This week, we’ll explore the concept of Strict mocks and see how they can help in cases like this.

Setting the Scene

Let’s imagine we’re writing a system to gather statistics from survey data. To keep things focussed in our example, our data model for a survey participant only has two properties: an ID number, and the participant’s age. We also have a repository with a method to get a participant's age from their ID. And finally, we have a service to calculate the average age of the participants we provide the IDs of.


 csharp
public interface IParticipantRepository
{
    int GetParticipantAge(int participantId);
}

public class SurveyParticipant
{
    public int Age { get; set; }
    public int Id { get; set; }
}

public class SurveyStatisticsService
{
    private readonly IParticipantRepository
        _participantRepository;

    public SurveyStatisticsService(
        IParticipantRepository participantRepository)
    {
        _participantRepository = participantRepository;
    }

    public double GetAverageAge(IList<int> participantIds)
    {
        double totalAge = 0;

        foreach (var id in participantIds)
        {
            var age = _participantRepository
                .GetParticipantAge(id);

            totalAge += age;
        }

        var average = totalAge / participantIds.Count;
        return average;
    }
}


Enter fullscreen mode Exit fullscreen mode

Writing a Test

With that in place, we want a test to check the logic for calculating the average age of participants. We wrote a few notes with a pen and paper while devising the system and want to encode the data shown in Table 1 into our test.

Table 1: Our test data. The average age is 28.5

Table 1: Our test data. The average age is 28.5

In the following code, you’ll notice we assert the result should be between 28.499 and 28.501. This is because GetAverageAge returns a double; comparing floating-point numbers to a specific value can lead to unexpected results due to how they are represented internally, e.g. 1 vs. 1.00000000000001.


 csharp
[Test]
public void CanReturnAverageAge()
{
    // Arrange

    var repositoryMock = new Mock<IParticipantRepository>();

    repositoryMock
        .Setup(r => r.GetParticipantAge(0))
        .Returns(27);

    repositoryMock
        .Setup(r => r.GetParticipantAge(1))
        .Returns(32);

    repositoryMock
        .Setup(r => r.GetParticipantAge(3))
        .Returns(29);

    var service = new SurveyStatisticsService(
        repositoryMock.Object);

    // Act

    var result = service.GetAverageAge(
        new[] { 0, 1, 2, 3 });

    // Assert

    Assert.That(result, Is.InRange(28.499, 28.501));
}


Enter fullscreen mode Exit fullscreen mode

However, we’ll find the test fails when we run it.



Expected: in range (28.499,28.501)
But was:  22.0d


Enter fullscreen mode Exit fullscreen mode

Is the logic in GetAverageAge wrong?

Or is there a problem in the test?

If you’re particularly keen-eyed, you may have spotted that we forgot to include a setup for participant 2. We saw in previous discussions that mocks will return an empty value of the corresponding data type if a setup hasn’t been provided. An empty value for double would be 0, and while invalid as a participant’s age, it’s perfectly usable in the context of calculating an average.

One way to catch this would be to verify that GetParticipantAge was called with all participant IDs in our test data set. However, as we forgot to add a setup, it’s also plausible that we might forget to add the corresponding verification step. To guard against this, we could use a Strict mock.

Strict Mocks

Mocks can be set up to behave in one of two ways in Moq. The first (and default) is Loose, where empty values are provided in the absence of method setups. We’ve seen this before with null being returned for reference types, and 0 for double.

The alternative behaviour is Strict. Instead of returning an empty value, a Strict mock will throw an exception when a method without a corresponding setup is called.

A mock’s behaviour can be specified using a constructor parameter and cannot be changed after instantiation. However, we can check its behaviour by looking at its (read-only) Behavior property. The following shows a modified version of our test, where the mock has Strict behaviour.


 csharp
[Test]
public void CanReturnAverageAge()
{
    // Arrange

    var repositoryMock = new Mock<IParticipantRepository>(
        MockBehavior.Strict);

    repositoryMock
        .Setup(r => r.GetParticipantAge(0))
        .Returns(27);

    repositoryMock
        .Setup(r => r.GetParticipantAge(1))
        .Returns(32);

    repositoryMock
        .Setup(r => r.GetParticipantAge(3))
        .Returns(29);

    var service = new SurveyStatisticsService(
        repositoryMock.Object);

    // Act

    var result = service.GetAverageAge(new[] { 0, 1, 2, 3 });

    // Assert

    Assert.That(result, Is.InRange(28.499, 28.501));
}


Enter fullscreen mode Exit fullscreen mode

The test stills fail when we run it. However, we’ll find the error message to be more indicative of where the problem lies.



Moq.MockException: 'IParticipantRepository.GetParticipantAge(2)
invocation failed with mock behavior Strict.

All invocations on the mock must have a corresponding setup.'

Enter fullscreen mode Exit fullscreen mode




Summary

By default, Mocks created in Moq return empty values when calling unconfigured methods. Strict mocks can save you time by highlighting missing setup steps when you run your tests. You can set a mock’s behaviour to Strict by passing the corresponding value as a constructor parameter. The mock will then throw an exception every time that a method without an explicit setup is called. Accidental omissions can be caught quickly, and you won’t end up wasting time unnecessarily debugging production logic.

Best of all, you can do this without explicitly writing Verify statements for your entire data set, keeping your test code tidy.


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!

💖 💪 🙅 🚩
ant_f_dev
Anthony Fung

Posted on August 23, 2023

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

Sign up to receive the latest update from our blog.

Related