There is a concept in testing known as spying. Spying includes validation that:

  1. a method was called
  2. a method was only called a specified number of times
  3. 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.