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.