There is a concept in testing known as spying. Spying includes validation that:
- a method was called
- a method was only called a specified number of times
- a method was called with specific arguments
Using the Moq framework, we can achieve spying with Verifiable
and Callback
. With these two tools, we can verify that methods were called and pluck out the variables that were used when making the call to make Assertions on them.
Spying Method Calls
Verifiable
is straight forward. You simply mark the Mock setup with Verifiable
and failure to execute will cause an exception.
var thing = new Thing() { Id = 1 };
var mockMapper = new Mock<IThingMapper>();
mockMapper.Setup(p => p.Save(thing)).Verifiable();
// do stuff
mockMapper.Verify()
If the Save
method is not called, the mock will throw an execption when we verify it. Pretty straightforward.
Spying Arguments
Spying arguments can be useful if you need to observe internal behavior of a method. For example, if you have logging in place that gets generated inside the method... you otherwise would not have acces to the property.
public void DoSomething()
{
var logEntry = new LogEntry() { Action = "Doing something" };
var result = // do something that gets logged
if(result) {
logEntry.Status = "Success";
} else {
logEntry.Status = "Failure";
}
// save the log entry
Container.GetInstance<ILogger>().Save(logEntry);
}
The Save
method is taking our logEntry
as an argument, so in theory we should be able to evaluate it! With Moq, the common attempt is to use It.Is
and specify arguments that match. Say you want to verify that Status == "Success"
, the following looks like it would work, but it doesn't:
mockLogger.Setup(p => p.Save(It.Is<LogEntry>(q => q.Status == "Success"))).Verifiable();
A better way to directly make assertions on the log value by using a Callback
to pluck out the arguments into local variables in your unit test:
LogEntry spiedLogEntry = null;
var mockLogger = new Mock<ILogger>();
mockLogger
.Setup(p => p.Save(It.IsAny<LogEntry>()))
.Callback<LogEntry>((p) =>
{
spiedLogEntry = p;
});
Assert.AreEqual("Success", spiedLogEntry.Status);
You can see here that the Callback has overrides that match the arguments supplied to the mocked method. You can then use the callback method to set local variables in your Unit Test method and make assertions in a "clean" manner.