Joe Buschmann

let topics = [csharp; specflow; fun]

Creating Mocks with F# Object Expressions

When I read about F# object expressions, the first thought that popped into my head was to use them to create mocks for unit tests.  For those of you who are not familiar with object expressions, they are similar to anonymous classes in Java.  They are the object equivalent of lambda functions, and they allow you to create objects that implement an interface or base class without having to declare a new class.  This is something that does not exist in C#, my primary language, where I have had to use a mock framework or define my own mock classes when writing unit tests.  There are downsides to both solutions.  Mock frameworks tend to be heavy-handed and are not built into the language.  Creating custom mock classes tends to lead to a large number custom classes declared outside of the unit test methods.

So that is why object expressions caught my eye.  As an exercise, I decided to use them to create mocks for some common unit testing scenarios.  First, I created a C# project with some fake business logic classes.  Next, I added an F# project to the solution for the unit tests.  The business logic code is listed below.  It contains a couple of interfaces representing a service provider and state container and a class, Order, that contains the “logic” to be unit tested.

// A simple class representing a product  
// in an order.  
public class Product  
{
    public Product(string productName)
    {
        ProductName = productName;
    }

    public string ProductName { get; private set; }
}

// A service request class.  
public class RetrieveAvailableProductsRequest  
{
}

// A service response class with a list of products.  
public class RetrieveAvailableProductsResponse  
{
    public RetrieveAvailableProductsResponse(
        IEnumerable<Product> products)
    {
        Products = products;
    }

    public IEnumerable<Product> Products
    {
        get;
        private set;
    }
}

// An interface for a service provider.  
public interface ICatalogService  
{
    RetrieveAvailableProductsResponse
        RetrieveAvailableProducts(
            RetrieveAvailableProductsRequest request);
}

// An interface for a state container.  
public interface IOrderState  
{
    IEnumerable<Product> Products { get; set; }
}

// A class containing simple "business logic".  
public class Order  
{
    public IEnumerable<Product> GetAvailableProducts(
        ICatalogService catalogService)
    {
        var request = new RetrieveAvailableProductsRequest();
        var response =
            catalogService.RetrieveAvailableProducts(request);

        return response.Products;
    }

    public void AddProductsToOrder(
        IEnumerable<Product> products,
        IOrderState orderState)
    {
        foreach (var product in products)
            AddProductToState(product, orderState);
    }

    protected virtual void AddProductToState(
        Product product,
        IOrderState orderState)
    {
        var products = new List<Product>(orderState.Products);
        products.Add(product);

        orderState.Products = products;
    }
}

There are two common mocking scenarios I come across when writing unit tests.  The first is to use a mock object in place of one that invokes a service, database operations, file system IO, or some other long-running process.  Both the mock and runtime objects implement an interface which is referenced by the business logic code instead of the runtime implementation.  Unit tests can substitute or “inject” their own implementation to avoid the expensive or unavailable runtime logic.  This is a common pattern called dependency injection.

The Order.AddProductsToOrder() method takes an interface, ICatalogService, that represents a web service exposing a product catalog.  In unit tests, the runtime implementation is replaced by a mock object that returns dummy values.  Creating this mock object in C# requires either a mock framework or a class definition.  Objects expressions in F# can simplify things by creating an object on the fly with very little overhead.  Below is a simple example.

let mockProducts = seq { yield new Product("Product 1")  
                         yield new Product("Product 2")
                         yield new Product("Product 3")
                         yield new Product("Product 4") }

[<Fact>]
let TestGetAvailableProducts() =  
  let mockCatalogService = { new ICatalogService with
    member x.RetrieveAvailableProducts(request) =
      new RetrieveAvailableProductsResponse(mockProducts) }
  let order = new Order()
  let products = order.GetAvailableProducts(mockCatalogService)
  Assert.Equal(4, Seq.length(products))
  Assert.Same(mockProducts, products)

The example starts by creating a sequence of dummy products.  The mock service, mockCatalogService, is created on the first three lines of the unit test method using an object expression which specifies that the new object implements the ICatalogService interface.  The implementation of ICatalogService.RetrieveAvailableProducts() returns the dummy products.  Next, the Order.GetAvailableProducts() method is invoked with the mock service, and finally, the last two lines verify that the products returned by the service are the same as those returned by GetAvailableProducts().

Below is another example where a mock is passed into Order.AddProductsToOrder() and updated.  The function, getMockOrderState(), uses an object expression to create a mock implementing IOrderState and stores a list of products in a reference cell.

let mockProducts = seq { yield new Product("Product 1")  
                         yield new Product("Product 2")
                         yield new Product("Product 3")
                         yield new Product("Product 4") }

let getMockOrderState() =  
  let productsInState = ref Seq.empty
  { new IOrderState with
    member x.Products with get() = !productsInState
                      and set(value) = productsInState := value }

[<Fact>]
let TestAddProductsToOrderUsingMockOrderState() =  
  let order = new Order()
  let mockOrderState = getMockOrderState()
  order.AddProductsToOrder(mockProducts, mockOrderState)
  Assert.Equal(4, mockOrderState.Products |> Seq.length)
  mockOrderState.Products |>
    Seq.iteri (fun i p ->
                 let i = i + 1
                 Assert.Equal("Product " + i.ToString(), p.ProductName))

Sometimes it is difficult to use dependency injection because the code may not be structured correctly, and it may be too complex for easy refactoring.  In these cases, I will often move the problematic code into its own virtual method.  The method can be overridden in a derived class that is used in the unit tests.  This is the second common scenario I encounter.

let mockProducts = seq { yield new Product("Product 1")  
                         yield new Product("Product 2")
                         yield new Product("Product 3")
                         yield new Product("Product 4") }

let getMockOrderState() =  
  let productsInState = ref Seq.empty
  { new IOrderState with
    member x.Products with get() = !productsInState
                      and set(value) = productsInState := value }

[<Fact>]
let TestAddProductsToOrder() =  
  let products = ref []
  let mockOrder =
    { new Order() with
      member x.AddProductToState(product, state) =
        products := product :: !products }
  let mockOrderState = getMockOrderState()
  mockOrder.AddProductsToOrder(mockProducts, mockOrderState)
  Assert.Equal(0, mockOrderState.Products |> Seq.length)
  Assert.Equal(4, !products |> List.length)

In the example above, the mockOrder object overrides the Order.AddProductToState() method and provides a simple implementation that stores the products in a reference cell.  This method is trivial; however, it demonstrates the idea.  The asserts prove that the local products reference cell was updated and not the order state.

I love how object expressions make creating mocks much easier.  This is a feature I hope makes its way into C#.  If you want the complete source code for the examples above, you can find it on GitHub.  You will need to install xUnit to run the unit tests.