Moq, according to its GitHub page, is “The most popular and friendly mocking framework for .NET”.
With this fake, you can then instruct the methods return values, or invoke behaviour.
Alternatively you can assert the methods were called, with a certain set of parameters.
How we verify these methods were called can affect the maintainability of our tests.
Let’s suppose we have this interface.
public interface ICustomerService | |
{ | |
int Save(Customer customer); | |
} |
Which is a dependency of the class we’re testing:
public class CustomerController : Controller | |
{ | |
private ICustomerService _customerService; | |
public CustomerController(ICustomerService customerService) | |
{ | |
_customerService = customerService; | |
} | |
public IActionResult Post(string firstName, string lastName) | |
{ | |
var customer = new Customer | |
{ | |
FirstName = firstName, | |
LastName = lastName | |
}; | |
var customerId = customerService.Save(customer); | |
return Created("/customer/" + customerId, customer); | |
} | |
} |
Our method takes a firstName and lastName and creates a `Customer` object using those parameters.
It then passes that `Customer` object to the `Save` method on the injected ICustomerService.
For the purposes of our test, we inject a mocked `ICustomerService`
The `Save` method returns an int, so we set our mocked method to return 1, when it is called with any `Customer` object.
Our unit test could look something like this:
//Arrange | |
var mockCustomerService = new Mock(); | |
var customerController = new CustomerController(mock.Object); | |
mockCustomerService.Setup(x=> x.Save(It.IsAny()) | |
.Returns(1); | |
//Act | |
customerController.Post("Alex", "Brown"); | |
//Assert | |
mockCustomerService | |
.Verify(x => x.Save( | |
It.Is( | |
c => | |
c.FirstName == "Alex" && | |
c.LastName == "Brown" | |
))); |
This test will of course pass. But, if we make a small change in our `CustomerController`
var customer = new Customer(); | |
customer.FirstName = firstName; | |
customer.LastName = firstName; //uh oh! |
Now our test will correctly fail.
However the exact reason for the failure isn’t clear:
Moq.MockException : Expected invocation on the mock at least once, but was never performed: x => x.Save(It.Is(c => c.FirstName == "Alex" && c.LastName == "Brown"))
All this tells us is that we didn’t call our dependency with an object that matches. It doesn’t give any indication as to why that object didn’t match.
Moq has a `Callback` function which we can use to assign our mocked `ICustomerService.Save` method to a variable outside of our setup.
Later on, we can use this variable for assertions.
//Arrange | |
var mockCustomerService = new Mock(); | |
var customerController = new CustomerController(mockCustomerService.Object); | |
Customer customerServiceSaveArg = null; | |
mockCustomerService | |
.Setup(x => x.Save(It.IsAny())) | |
.Returns(1) | |
.Callback(c => customerServiceSaveArg = c); | |
//Act | |
var result = customerController.Post("Alex", "Brown"); |
And now, we can individually assert on each parameter:
Assert.IsNotNull(customerServiceSaveArg); | |
Assert.AreEqual("Alex", customerServiceSaveArg.FirstName); | |
Assert.AreEqual("Brown", customerServiceSaveArg.LastName); |
If we run this test (without fixing our code from the previous failure) it now gives a much clearer failure:
Expected string length 5 but was 4. Strings differ at index 0. Expected: "Brown" But was: "Alex" -----------^