Post

Fluently mocking fluent APIs

In .NET on December 15, 2008 by Sid Tagged: ,

The best thing about fluent-style APIs is that they are readable and flow easily. Consider the following for example:

customer.NewOrder
.With(1, “ONE”)
.With(2, “TWO”)
.With(3, “THREE”)
.PriorityRush();

The API used above is clearly more readable than:

Order o1 = new Order();
customer.AddOrder(o1);
OrderItem item1 = new OrderItem(1, “ONE”);
o1.Add(item1);
OrderItem item2 = new OrderItem(2, “TWO”);
o1.Add(item2);
OrderItem item3 = new OrderItem(3, “THREE”);
o1.Add(item3);
o1.SetRush();

Another reason I like them is that fluent style APIs are easier to pick up. You can rely on intellisense to guide you more as you have to worry about lesser number of class and method names.

So they’re nice, but I hate to see code like the following that has to do a lot of setting up before you can actually make a call to the function you wish to test from your unit-test:

[TestMethod()]
public void PlacePriorityOrderTest()
{
using (Mockery mocks = new Mockery())
{
int itemID = 42;
string itemDescription = “SOMETHING”;

Program target = new Program();

// Start setting up NMock expectations…
ICustomer customer = mocks.NewMock();
IOrderActions mockOrderActions = mocks.NewMock();
Expect.Once.On(customer).GetProperty(“NewOrder”).Will(Return.Value(mockOrderActions));
IOrderActions mockOrderItemActions = mocks.NewMock
();
Expect.Once.On(mockOrderActions).Method(“With”).With(itemID, itemDescription).Will(Return.Value(mockOrderItemActions));
Expect.Once.On(mockOrderItemActions).Method(“PriorityRush”);
// End setting up NMock expectations…

// Call the function we wish to test
target.PlacePriorityOrder(customer, itemID, itemDescription);
}
}

Here is the function that the test above is exercising:

public void PlacePriorityOrder(ICustomer customer, int itemID, string itemDescription)
{
customer.NewOrder.With(itemID, itemDescription).PriorityRush();
}

The test is just making sure that the item-id and item-description passed to PlacePriorityOrder are used to create a new order with priority-rush. There’re probably going to be many tests that exercise the same fluent-API and they’d all be doing similar setting up of mock objects.

It would be better if the expectation was specified in a fluent manner as well.

[TestMethod()]
public void PlacePriorityOrderTest2()
{
using (Mockery mocks = new Mockery())
{
int itemID = 42;
string itemDescription = “SOMETHING”;

Program target = new Program();

MockCustomer mockCustomer = MockCustomer.Record(mocks);
mockCustomer.NewOrder.With(itemID, itemDescription).PriorityRush();

target.PlacePriorityOrder(mockCustomer.Start(), itemID, itemDescription);
}
}

Notice the call to MockCustomer.Record to return a mock ICustomer instance that can be used to record calls to various methods that the test expects to be made. Also note that mockCustomer.Start is used to retrieve the mock ICustomer instance that’s actually used by the function under test.

The zip archive here contains code demonstrating how this can be done.

Leave a Reply