Click here to Skip to main content
14,971,409 members
Articles / DevOps / Testing
Article
Posted 14 Oct 2014

Stats

45.8K views
118 downloads
5 bookmarked

moq.Callback(), The Unknown

Rate me:
Please Sign up or sign in to vote.
4.96/5 (9 votes)
14 Oct 2014CPOL5 min read
When and how to use the Callback method on Moq in your unit tests.

Introduction

More and more often I do see people having trouble testing certain type of code. As a result, code coverage is dropping down, unverified logics are shown up, lowering the quality and rising frustrations. This is what pushed me to write this post and describe this kind of situations and a decent solution to it.

This kind of situations are commonly found in all the cases in which the tested unit manipulates the arguments that are passed to our mock. In this case we need to verify that this transformation did what we expected it to do. However, it ain't that simple and straightforward as it seems. As code speaks more than thousand words, let's illustrate this with an example.

Consider the following class:
C#
public class ProductService
{
    private readonly IOrderRepository m_orderRepository;

    public ProductService(IOrderRepository orderRepository)
    {
        m_orderRepository = orderRepository;
    }

    public List<Product> GetProducts(int customerId, int orderId)
    {
        OrderSearchCriteria orderSearchCriteria = new OrderSearchCriteria
        {
            OrderId = customerId // THIS IS THE PROBLEM WE ARE GOING TO SEARCH FOR

            // Set some other search criteria...
        };

        Order retrievedOrder = m_orderRepository.GetOrder(orderSearchCriteria);

        // Do something else
        return retrievedOrder.Products;
    }
}

What you can see here is a hypothetical product service that we are going to test. More precisely we are going to write unit tests for the GetProducts method. What this method does in particular is composing another object that will be passed to our dependency, the order repository. Now you can argue that this is a bad practice, that the object composition should be handled in a different manner, as usually in this cases the single responsibility principle is not met. And you are right, but we do not live in a perfect world and often we can't easily change what is already there. However, we need to keep extending and improving our software.

Still, I do need to write a test for that method. What should I do, how do I spot this bug that we just introduced?

There are two ways of writing a unit test that will test, verify and spot our bug. Let's start with the first one.

Shaping an expected instance

We can tackle this problem by setting up manually, in our test, an instance of OrderSearchCriteria class, as we expect it to be, base on the parameters that we are passing in, and make sure that our mock accepts only an instance that equals ours, on purpose created class.

Let's check our unit test.

C#
[TestMethod]
public void GetProducts_Creates_OrderSearchCriteria_Correctly()
{
    const int customerId = 56789;
    const int orderId = 12345;

    OrderSearchCriteria orderSearchCriteria = new OrderSearchCriteria
    {
        OrderId = orderId
    };

    Mock<IOrderRepository> orderRepositoryMock = new Mock<IOrderRepository>();
    orderRepositoryMock
        .Setup(m => m.GetOrder(orderSearchCriteria))
        .Returns(new Order());

    ProductService sut = new ProductService(orderRepositoryMock.Object);

    List<Product> result = sut.GetProducts(customerId, orderId);
}

Now, first thing first. In order this example to even work, your parameter class needs to implement the equality members. This is necessary as Moq, in order to determine equality of parameters, rightly, relays on Equals() method.

Another disadvantage of this technique is the fact that construction of our own object can sometimes be hard or even not possible. Not to even mention the maintenance problem we are going to introduce.

As Moq in the case of wrong parameter will return a null from the method call, often null value is managed and interpreted as a possible state. In that case it will be very hard or impossible to discover our bug.

Luckily there is a cleaner way to approach this kind of situations.

Extracting the parameter via Callback method

As it is not often used, many developers tend to ignore the Callback() method that is provided by Moq framework. In this kind of situations it can be very handy.

Check out the following test.

C#
[TestMethod]
public void GetProducts_Creates_OrderSearchCriteria_Correctly_2()
{
    const int customerId = 56789;
    const int orderId = 12345;

    OrderSearchCriteria recievedOrderSearchCriteria = null;

    Mock<IOrderRepository> orderRepositoryMock = new Mock<IOrderRepository>();
    orderRepositoryMock
        .Setup(m => m.GetOrder(It.IsAny<OrderSearchCriteria>()))
        .Returns(new Order())
        .Callback<OrderSearchCriteria>(o => recievedOrderSearchCriteria = o);

    ProductService sut = new ProductService(orderRepositoryMock.Object);

    List<Product> result = sut.GetProducts(customerId, orderId);

    Assert.IsNotNull(recievedOrderSearchCriteria);
    Assert.AreEqual(orderId, recievedOrderSearchCriteria.OrderId);
}

You can see that I'm setting up my mock and I'm specifying what the callback should do. What I'm telling him in this case is that for the parameter of type OrderSearchCriteria, once the method is invoked, copy it to the locally defined object called recievedOrderSearchCriteria. This will give me the possibility to check what came in the call of the GetOrder method, and verify that is what I expect to receive.

Once I start my assertions I do a check on the recievedOrderSearchCriteria and I do make sure that what came in, is what I do expect.

This test will fail and we will succeed in our intent. Also the message that we are receiving is far way clearer than one in the previous example. It states at this moment

Assert.AreEqual failed. Expected:<12345>. Actual:<56789>.
Beside this we are actually asserting the expected result thus specifying behavior in an explicit way. Now, this on my opinion is much better!

Other considerations about the Callback method

For a less experienced developers, I'll also make an example of how to get a callback working if you have more then one parameter accepted by your mocked object.

I'm going to extend our IOrderRepository interface by adding a overload of the GetOrder method that accepts two parameters. Also I will implement another method on ProductService class that uses this newly created method.

C#
public interface IOrderRepository
{
    Order GetOrder(OrderSearchCriteria searchCriteria);

    Order GetOrder(int orderId, bool archieved);
}

public List<Product> GetProducts(int orderId)
{
    Order retrievedOrder = m_orderRepository.GetOrder(orderId, true);

    return retrievedOrder.Products;
}
In order to mock my order repository and have on callback the necessary values, the following test is used.
C#
[TestMethod]
public void GetProducts_With_Archieved_Orders()
{
    const int orderId = 12345;

    int receivedOrderId = 0;
    bool receivedArchieved = false;

    Mock<IOrderRepository> orderRepositoryMock = new Mock<IOrderRepository>();
    orderRepositoryMock
        .Setup(m => m.GetOrder(It.IsAny<int>(), It.IsAny<bool>()))
        .Returns(new Order())
        .Callback<int, bool>((o, a) =>
        {
            receivedOrderId = o; 
            receivedArchieved = a;
        });

    ProductService sut = new ProductService(orderRepositoryMock.Object);

    List<Product> result = sut.GetProducts(orderId);

    Assert.AreEqual(orderId, receivedOrderId);
    Assert.AreEqual(true, receivedArchieved);
}

As you can see, I just added an extra type in my generic definition and then adjusted my lambda expression accordingly. If more than two parameters are required, you can just follow the same pattern and define them as many as need. At example .Callback((o, a, i) etc.

You always need to respect the exact parameter signature of the method you are setting up. The number of parameters accepted by the mocked method need to mach in type, order and number the parameters accepted by your mocked method.

There is another way to express the same statement, just by using the lambda expression instead of generics. I can rewrite the above examples in the following way:

C#
.Callback((OrderSearchCriteria o) => recievedOrderSearchCriteria = o);

// ...

.Callback((int o, bool a) =>
{
    receivedOrderId = o;
    receivedArchieved = a;
});
The effect is the same, it is only about your preference which of the two ways to use.

It is also possible to define it before and after the method invocation and as I can't think of a nice example I will just provide the one from the Moq documentation:

C#
// callbacks can be specified before and after invocation
mock.Setup(foo => foo.Execute("ping"))
    .Callback(() => Console.WriteLine("Before returns"))
    .Returns(true)
    .Callback(() => Console.WriteLine("After returns"));

Conclusion

Each time you need to check the arguments that are passed to the method you are setting up, Callback() will help you get to them. Also, if you need to execute any code before or after the method is invoked, Callback() will let you do so. Hopefully you are not going to use it on a daily basis, but it is handy to know about it as sooner or later you are going to face a situation where a Callback() will help you achieve your goal.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Mario Majčica
Software Developer (Senior)
Netherlands Netherlands
An accomplished software engineer specialized in object-oriented design and analysis on Microsoft .NET platform with extensive experience in the full life cycle of the software design process.
Experienced in agile software development via scrum and kanban frameworks supported by the TFS ALM environment and JIRA. In depth know how on all automation process leading to continuous integration, deployment and feedback.
Additionally, I have a strong hands-on experience on deploying and administering Microsoft Team Foundation Server (migrations, builds, deployment, branching strategies, etc.).

Comments and Discussions

 
GeneralMy vote of 5 Pin
DPRobinson3-Nov-15 23:26
MemberDPRobinson3-Nov-15 23:26 
QuestionVoted Excellent Pin
C Pottinger10-Jun-15 7:18
MemberC Pottinger10-Jun-15 7:18 
GeneralMy vote of 5 Pin
codefabricator16-Oct-14 6:40
Membercodefabricator16-Oct-14 6:40 
QuestionFirst unit test example seems to be missing the Assert.AreEqual Pin
aaronlawrence15-Oct-14 10:37
Memberaaronlawrence15-Oct-14 10:37 
AnswerRe: First unit test example seems to be missing the Assert.AreEqual Pin
Mario Majčica15-Oct-14 21:48
professionalMario Majčica15-Oct-14 21:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.