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;
}
}
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.
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));
}
However, we’ll find the test fails when we run it.
Expected: in range (28.499,28.501)
But was: 22.0d
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));
}
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.'
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!
Top comments (2)
I tkink you have to call verifyall at the end of test on repository
Hi deexter.
Thanks for mentioning
VerifyAll
. This is indeed a shortcut in place of callingVerify
on every method to check that each setup has been called. However, this won't catch cases where a setup is accidentally missing.