Kash Farooq's software development blog

.NET Developer

Using Moq callbacks to check complex arguments sent to a dependency

Posted by Kash Farooq on June 16, 2013

I’ve written before about the various ways you can test the contents of a complex parameter sent to a dependency. The messages you see in RhinoMocks and Moq when a test fails aren’t always clear.

You can use Moq callbacks to give clear test failure messages.

Consider the system under test. Your creating an order, and using some of the data in the order object to create a customer. Obviously, you would have written this code test-driven – the aim of the test would be to ensure the customer object is created properly.

public class OrderService
{
  private readonly ICustomerService _customerService;

  public OrderService(ICustomerService customerService)
  {
    _customerService = customerService;
  }

  public void CreateOrder(Order order, int createdBy)
  {
     var customer = new Customer
        {
           FirstName = order.FirstName,
           LastName = order.FirstName //intentional bug
        };
    _customerService.Save(customer, createdBy);

    //TODO: do some other stuff with the order.
  }
}

In a test, let’s just use Moq’s Verify method and you’ll see what I mean about clear failure messages:

[Test]
public void WhenCreatingAnOrderSaveTheCustomerDetails()
{
  const int orderCreatedByPersonId = 1;

  var customerServiceMock = new Mock<ICustomerService>();

  var orderService = new OrderService(customerServiceMock.Object);
  orderService.CreateOrder(new Order { FirstName = "John", LastName = "Smith", OrderItems = new List<OrderItem>() }, orderCreatedByPersonId);

  customerServiceMock.Verify(x => x.Save(It.Is<Customer>(y => y.FirstName == "John" && y.LastName == "Smith"), orderCreatedByPersonId));
}

And this is the error you’ll see:

Moq.MockException :
Expected invocation on the mock at least once, but was never performed: x => x.Save(It.Is<Customer>(y => y.FirstName == "John" && y.LastName == "Smith"), 1)
No setups configured.

Performed invocations:
ICustomerService.Save(Customer, 1)

I don’t like this error message. You can’t tell what caused the failure. You can see that¬†ICustomerService.Save was called with a Customer object (and the number 1), bit you can’t see why the test failed.

You could split up the Verify – use two verify calls to help pinpoint where it has failed:

[Test]
public void WhenCreatingAnOrderSaveTheCustomerDetails_SplitVerify()
{
  const int orderCreatedByPersonId = 1;

  var customerServiceMock = new Mock<ICustomerService>();

  var orderService = new OrderService(customerServiceMock.Object);
  orderService.CreateOrder(new Order { FirstName = "John", LastName = "Smith", OrderItems = new List<OrderItem>() }, orderCreatedByPersonId);

  customerServiceMock.Verify(x => x.Save(It.Is<Customer>(y => y.FirstName == "John"), orderCreatedByPersonId));
  customerServiceMock.Verify(x => x.Save(It.Is<Customer>(y => y.LastName == "Smith"), orderCreatedByPersonId));
}

And this is the error you’ll see. It is a lot better as you now know exactly which part of the verify call failed (i.e. the last name of the customer).

Moq.MockException :
Expected invocation on the mock at least once, but was never performed: x => x.Save(It.Is<Customer>(y => y.LastName == "Smith"), 1)
No setups configured.

Performed invocations:
ICustomerService.Save(Recommendations.Customer, 1)

Now let’s use callbacks instead t give you a much more obvious error message. In your arrange part of the test, you set up a callback to catch the data sent to the mocked dependency:

[Test]
public void WhenCreatingAnOrderSaveTheCustomerDetails_UsingCallbacks()
{
  const int orderCreatedByPersonId = 1;

  var customerServiceMock = new Mock<ICustomerService>();
  Customer actualCustomer = null;
  int actualSavedBy=0;
  customerServiceMock.Setup(x => x.Save(It.IsAny<Customer>(), It.IsAny<int>())).Callback<Customer,int>((customer, person) =>
                              {
                               actualCustomer = customer;
                               actualSavedBy = person;
                              }); //set up the callback to catch the parameters sent to Save()

  var orderService = new OrderService(customerServiceMock.Object);
  orderService.CreateOrder(new Order { FirstName = "John", LastName = "Smith", OrderItems = new List<OrderItem>()},orderCreatedByPersonId);

  actualSavedBy.Should().Be(orderCreatedByPersonId);
  actualCustomer.Should().NotBeNull();
  actualCustomer.FirstName.Should().Be("John");
  actualCustomer.LastName.Should().Be("Smith");
}

Now you’ll see a much nicer error message (from Fluent Assertions):

Expected string to be "Smith", but "John" is too short.
About these ads

One Response to “Using Moq callbacks to check complex arguments sent to a dependency”

  1. Jim Ballard said

    Nice posts. Thanks.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: