In unit testing, it’s common to verify that certain methods are called with specific parameters. Moq provides a convenient way to do this with the Verify method. However, relying solely on Verify can sometimes lead to less informative error messages. This is where Moq’s Callback method can be very useful.
Let’s see an example to illustrate this. First, we set up our classes and interfaces:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using FluentAssertions;
using Moq;
namespace MoqCallBackExample;
public class User
{
public Guid Id { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
public interface IUserDatabase
{
void Add(User user);
}
public class UserManager
{
private readonly IUserDatabase userDatabase;
public UserManager(IUserDatabase userDatabase)
{
this.userDatabase = userDatabase;
}
public void CreateUser(Guid id, string firstName, string lastName)
{
// do some work
this.userDatabase.Add(new User
{
FirstName = firstName, // lets create a bug here
LastName = firstName,
Id = id,
});
}
}
We have a User class, an IUserDatabase interface, and a UserManager class that uses IUserDatabase. The UserManager has a method CreateUser which creates a User and adds it to the database. Note that we intentionally introduced a bug: the LastName property is incorrectly set to firstName.
Traditional Verification
A traditional approach to verify if the Add method was called with the correct parameters looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[TestMethod]
public void UserManagerAddsUserToTheDatabase_CouldBe()
{
var id = Guid.NewGuid();
var firstName = "Hello";
var lastName = "World";
sut.CreateUser(id, firstName, lastName);
databaseMock.Verify(x => x.Add(It.Is<User>(y =>
y.Id == id &&
y.LastName == lastName &&
y.FirstName == firstName)
), Times.Once);
}
This test fails with the following error:
1
2
3
4
5
6
7
8
Test method TestProject1.MoqCallBackExampleTest.UserManagerAddsUserToTheDatabase_CouldBe threw exception:
Moq.MockException:
Expected invocation on the mock once, but was 0 times: x => x.Add(It.Is<User>(y => (y.Id == id && y.LastName == lastName) && y.FirstName == firstName))
Performed invocations:
Mock<IUserDatabase:2> (x):
IUserDatabase.Add(User)
The error message indicates that the expected invocation did not occur, but it doesn’t provide specific details about what went wrong with the User object.
Using Moq’s Callback
To improve the test’s clarity, we can use Moq’s Callback to capture the User object passed to the Add method and then perform assertions on it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[TestMethod]
public void UserManagerAddsUserToTheDatabase_Better()
{
var id = Guid.NewGuid();
var firstName = "Hello";
var lastName = "World";
User createdUser = null;
databaseMock.Setup(x => x.Add(It.IsAny<User>()))
.Callback<User>(x => createdUser = x);
sut.CreateUser(id, firstName, lastName);
createdUser.Should().NotBeNull();
createdUser.FirstName.Should().Be(firstName);
createdUser.LastName.Should().Be(lastName);
createdUser.Id.Should().Be(id);
}
With this approach, if the test fails, the error message will be more informative:
1
2
Message:
Expected createdUser.LastName to be "World", but "Hello" differs near "Hel" (index 0).
This message clearly indicates that the LastName was incorrectly set to “Hello” instead of “World”, pinpointing the exact issue in the CreateUser method.
Conclusion
Using Moq’s Callback method allows you to capture and inspect arguments passed to mocked methods, providing more detailed and helpful error messages when tests fail. This can make debugging much easier and faster. In our example, Callback helped us identify that the LastName was incorrectly assigned, something that was less clear with the traditional Verify approach.
By incorporating Callback into your testing strategy, you can improve the robustness and clarity of your unit tests, making your test suite more effective at catching bugs and easier to maintain.