BDD

.NET Musings

Wandering thoughts of a developer, architect, speaker, and trainer

NAVIGATION - SEARCH

Sequential and Recursive Mocking with JustMock Free Edition

Sequential Mocking

One of the trickiest types of code to mock out for testing is recursive methods.  In a typical recursion, the same method is called multiple times, and each time there are typically different parameters passed in, and different return values.  To easily handle this, JustMock gives you two options:

  • Adding InSequence() to each arrange
  • Chaining Returns() calls together.

To illustrate the first option, look at the following test:

[Test]
public void Show_Sequential_InSequence_Arrange_Option()
{
    var service = Mock.Create<IService>();
    Mock.Arrange(() => service.GetSum(Arg.AnyInt, Arg.AnyInt))
              .Returns(4).InSequence();
    Mock.Arrange(() => service.GetSum(Arg.AnyInt, Arg.AnyInt))
              .Returns(8).InSequence();
    Assert.AreEqual(4, service.GetSum(3, 10));
    Assert.AreEqual(8, service.GetSum(3, 2));
}

By adding InSquence to the arrangements, the framework will match each arrange in order (note that the order they are setup in the test is the order that they will execute).  While this is the “longhand” version, it does give the full power of argument matching to fine tune the arrangements.

If all of the parameters will be the same, or can be ignored, the Returns() chaining shortcut can be used. This is the same test with the streamlined version:

[Test]
public void Show_Chained_Returns_Arrange_Option()
{
    var service = Mock.Create<IService>();
    Mock.Arrange(() => service.GetSum(Arg.AnyInt, Arg.AnyInt))
    .Returns(4)
    .Returns(8);
    Assert.AreEqual(4, service.GetSum(3, 10));
    Assert.AreEqual(8, service.GetSum(3, 2));
}

Recursive Mocking

Another situation that can cause friction in testing is when there are additional dependencies that must be setup on the mock itself. A common example of this is fluent interfaces, where multiple methods are chained together. 

To illustrate this, let’s expand the IShoppingCartRepository to include an IInventoryRepository property. We presume that this dependency will be injected into the ShoppingCartRepository, keeping us from having to inject too many dependencies into the SecurityHandler.  Since we have isolated our system under test from needing instantiated dependencies, we don’t really care how the dependency gets resolved!

public interface IShoppingCartRepository
{
    Cart LoadCart(int userID);
    IInventoryRepository InventoryRepository { get; set; }
}
public interface IInventoryRepository
{
    bool ValidateInventory(int productID);
}

We also add a method for adding products to the user’s cart:

public bool AddProductToCart(int productID)
{
    if (_cartRepo.InventoryRepository.ValidateInventory(productID))
    {
        ShoppingCart.Items.Add(productID);
        return true;
    }
    return false;
}

To test this code, we need two dependency objects arranged.  Recursive Mocking with JustMock streamlines this process by implicitly creating chained mocks for us.  We still need to create the first mock object, and all methods need to be arranged.  The following test shows this.

[Test]
public void RecursiveMockTest()
{
    var cartRepo = Mock.Create<IShoppingCartRepository>();
    Cart cart = new Cart();
    Mock.Arrange(() => cartRepo.LoadCart(Arg.AnyInt)).Returns(cart);
    Mock.Arrange(() => cartRepo.InventoryRepository.ValidateInventory(4))
    .Returns(true);
    UserHandler handler = new UserHandler(null, cartRepo);
    handler.AddProductToCart(4);
    Assert.AreEqual(handler.ShoppingCart.Items.Count, 1);
}

JustMock implicitly creates a mock for the IInventoryRepository property of the ShoppingCartRepository with the second arrangement.  This prevents the nasty problem of injecting a mock object into a mock object, and makes the tests much easier to read.

Summary

Both of the features covered in this post are great tools to make your tests cleaner, easier to read, and enable testing of code that might otherwise be extremely difficult or impossible to test without significant refactoring.

Happy Coding!

Managed Windows Shared Hosting by OrcsWeb