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:
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
};
Order retrievedOrder = m_orderRepository.GetOrder(orderSearchCriteria);
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.
[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.
[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.
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.
[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:
.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:
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.