BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Testing
.NET 3.5+

Using Callbacks with Moq

Some scenarios are difficult to test when using mocks with simple expectations. When the functionality of a dependency is complex, it can be useful to inject callback code that is executed when the mock object is accessed.

Callbacks

A callback is a piece of code that is passed into to a method parameter to be executed from within that method. Callbacks allow you to extend the functionality of such methods. When using Moq to create test doubles, you can supply callback code that is executed when an expectation is met. This allows you to create more complex tests than would be supported with mocks that simply return values or raise events.

A common use of a callback in a test double is where you wish to mock an object containing a method that modifies an object that is provided to its parameter. You can set up a callback that makes a specific update when the method is called, or one that performs a change using simplified functionality. This can make it much easier to create robust unit tests that execute quickly.

Example Code

To demonstrate the use of callbacks we need some sample code and a unit test. Create a new project and add the following interface and classes. These types might be used inside a ticketing system. The TicketGenerator class has a method that creates a Ticket object for the provided Person. As part of the process, the person's name is formatted and updated within the Person object, using a class that implements IPersonNameCleaner. It is this interface that we will mock.

public interface IPersonNameCleaner
{
    void Clean(Person person);
}


public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}


public class Ticket
{
    public string Name { get; set; }
}


public class TicketGenerator
{
    IPersonNameCleaner _cleaner;

    public TicketGenerator(IPersonNameCleaner cleaner)
    {
        _cleaner = cleaner;
    }

    public Ticket GenerateTicket(Person person)
    {
        _cleaner.Clean(person);
        return new Ticket
        {
            Name = string.Format("{0} {1}", person.FirstName, person.LastName)
        };
    }
}

Let's create a test that a generated ticket contains the correct name. It should be based upon the FirstName and LastName properties of the Person, after it has been updated by the injected IPersonNameCleaner.

Create and run the following test. Here the cleaner is assumed to join together the initial letter of the first name and the full surname. The test fails because the mocked cleaner does not modify the person's name before it is read by the TicketGenerator.

[Test]
public void AGeneratedTicketHasTheCorrectName()
{
    Person person = new Person { FirstName = "Bob", LastName = "Smith", Age = 18 };

    Mock<IPersonNameCleaner> _mockCleaner = new Mock<IPersonNameCleaner>();

    TicketGenerator generator = new TicketGenerator(_mockCleaner.Object);

    Ticket ticket = generator.GenerateTicket(person);

    Assert.AreEqual("B Smith", ticket.Name);
}

Declaring a Callback

To inject a callback into a mock object, you use the CallBack method, passing an Action delegate. You can use this in place of, or in addition to, a Returns call. For example, the updated test below configures the mock using a lambda expression so that when the Clean method is called with any Person object, the variable, "person", is amended to change the FirstName to "B".

Update the code, as shown below, and re-run the test. It should now pass.

[Test]
public void AGeneratedTicketHasTheCorrectName()
{
    Person person = new Person { FirstName = "Bob", LastName = "Smith", Age = 18 };

    Mock<IPersonNameCleaner> _mockCleaner = new Mock<IPersonNameCleaner>();
    _mockCleaner.Setup(m => m.Clean(It.IsAny<Person>()))
                .Callback(() => person.FirstName = "B");

    TicketGenerator generator = new TicketGenerator(_mockCleaner.Object);

    Ticket ticket = generator.GenerateTicket(person);

    Assert.AreEqual("B Smith", ticket.Name);
}

Using the Provided Arguments in a Callback

In the above example, the callback does not need to use the Person object that is provided to the mock because only one Person is ever created. For more complex scenarios, you can pass the called method's parameter values as arguments of the delegate. This lets you generate results based upon the values used to call the method.

Consider the modified test below:

[Test]
public void AGeneratedTicketHasTheCorrectName()
{
    Person person = new Person { FirstName = "Bob", LastName = "Smith", Age = 18 };

    Mock<IPersonNameCleaner> _mockCleaner = new Mock<IPersonNameCleaner>();
    _mockCleaner.Setup(m => m.Clean(It.IsAny<Person>()))
                .Callback<Person>(p => p.FirstName = p.FirstName[0].ToString());

    TicketGenerator generator = new TicketGenerator(_mockCleaner.Object);

    Ticket ticket = generator.GenerateTicket(person);

    Assert.AreEqual("B Smith", ticket.Name);
}

The updated callback includes a generic type parameter that matches the type of the argument of the method in the expectation. This becomes the type of the argument of the delegate, which is passed to the "p" of the lambda expression. Using this syntax means that the Person provided to the method is now being updated, using values from its own properties. If you were to pass several Person objects to the mocked method, each would update appropriately.

20 August 2015