Mocking Revisited: Test-Driven Design

An entry about arcitechture | tdd Publication date 27. March 2008 16:27

A month or so ago, I held a talk at the local .NET user group meeting about using mocking to simplify unit testing, which I also wrote up as a post on the blog. In it, I showed both how we could manually break the dependency on some external component in order to successfully test our code in isolation, and I also did an example showing how the mocking framework Typemock Isolator could be used to get around having to refactor my code in order to test it. In this post, I'd like to revisit the example and show how the code might have looked different, had it been written using strict(ish) test-driven design principles.

Test First

When doing test-driven development, we want to focus on incrementally satisfying a single requirement at a time, ensuring the isolated correctness of each before moving on to the next. So, the first thing we need to do, is write our first test (which is the code-equivalent of a requirement):

[TestMethod]
public void NewOrderHasZeroOrderLines()
{
    Order order = new Order();
    Assert.AreEqual(0, order.OrderLines.Count);
}

Notice the Order class and its OrderLines property? We've not even declared the classes for those yet, so this test won't even compile. Following the tenents of TDD, we now want to fix that, and make the test succeed (to be pedantic, we should really make it compile and fail first, then refactor our code and make it succeed - but I won't go out of my way just to make a trivial test fail first; we'll get plenty of that later!):

public class Order
{
    private readonly List<OrderLine> _orderLines = new List<OrderLine>();
 
    public IList<OrderLine> OrderLines { get { return _orderLines; } }
}

 

Obviously, that's not how we expect our Order class to end up looking like - but for now, it satisfies all (the one) tests we have - and that means it satisfies the current requirements we have. What we do next, is add another test to verify another requirement:

[TestMethod]
public void NewOrderLineDefinesOrderLine()
{
    const int productId = 0;
    const int amount = 1;
    const decimal cost = 25;
 
    OrderLine line = new OrderLine(productId, amount, cost);
 
    Assert.AreEqual(productId, line.ProductId);
    Assert.AreEqual(amount, line.Amount);
    Assert.AreEqual(cost, line.Cost);
}

 

To pass this one, we need to refactor the OrderLine class, adding the three properties and initializing them from its constructor:

public class OrderLine
{
    public OrderLine(int productId, int amount, decimal cost)
    {
        this.ProductId = productId;
        this.Amount = amount;
        this.Cost = cost;
    }
 
    public int ProductId { get; set; }
    public int Amount { get; set; }
    public decimal Cost { get; set; }
}

 

Okay, so far the tests aren't very interresting. We're getting somewhere now, though. The original example I blogged about last time concerned itself with calculating the cost of an order. This requirement isn't straight forward to verify in a single test, as it contains several edge cases (zero orderlines, one orderline, many orderlines, etc). We should test each of these cases, then. Let's start with the simplest one first:

[TestMethod]
public void CostForZeroOrderLinesShouldBeZero()
{
    Order order = new Order();
 
    Assert.AreEqual(0, order.CalculateTotalCost());
}

 

This first one is easilly satisfiable through the following implementation of CalculateTotalCost:

public decimal CalculateTotalCost()
{
    return 0;
}

 

When we embelish upon the requirement with the following test, though...

[TestMethod]
public void CostForOrderWithSingleOrderLineShouldBeCostOfOrderLine()
{
    Order order = new Order();
    OrderLine line = new OrderLine(0, 1, 25);
    order.OrderLines.Add(line);
 
    Assert.AreEqual(line.Amount*line.Cost, order.CalculateTotalCost());
}

 

... we'll see that we need to refactor our CalculateTotalCost method to make this succeed:

public decimal CalculateTotalCost()
{
    return this.OrderLines.Sum(line => line.Amount * line.Cost);
}

 

Now that is important, what we had to do there - it is what might be said to be the essence of test-driven development, the cornerstone in the way of writing code that TDD promotes: write a test, watch it fail, then refactor to make all tests pass; rinse and repeat.

Inversion of Control

This next test that we'll be adding, will showcase how writing the code using TDD will make the end result differ in implementation from the original example. We'll be adding the idea of shipping rules; remember that this is what caused the problem in my original example because these shipping rules were to be dynamically instansiated based upon some configuration stored in a database. The CalculateShippingRule method thus needs to be able to access this configuration and get the appropriate ruleset. Thinking back to the original example, this responsibility was delegated to a static Rules class. That was what made it really hard to test, because the code then had a tight dependency on that static class, making it impossible to isolate what we wanted to test from it. Last time, I explained how the code could be refactored using a variant of the service locator pattern so that I could isolate the CalculateTotalCost method and test it properly, and I also showed how using TypeMock could let me test the original code in just a few lines of code without having to change the original implementation at all - everyone watching the presentation loved that solution, of course ;)

But here and now, since we're writing the test before implementing the feature, we're forced to think about the coupling we'll be introducing up front. We could go down the path we did last time, with a service locator - but a more natural choice for a TDD-based implementation is to choose a different implementation of the inversion of control principle called dependency injection. Basically, we'll see that our Order class will depend on some ruleset factory - so we'll add this factory as a constructor parameter on the Order class. This makes it very discoverable what the Order class is dependent on to function, but it also makes it harder to instantiate it because of the explicit dependency - anyone creating an Order instance will also need to know how to create a ruleset factory. We'll look at how to solve this problem later using an IoC container, but for now let's focus on implementing the feature. Test first:

[TestMethod]
public void CostForSingleOrderLineAndSingleShippingRule()
{
    const decimal shippingCost = 10;
 
    Mock<IShippingRule> shippingRule = new Mock<IShippingRule>();
    shippingRule.Expect(r => r.CalculateCost(It.IsAny<Order>())).Returns(shippingCost);
 
    Mock<IRules> rules = new Mock<IRules>();
    rules.Expect(r => r.Find<IShippingRule>()).Returns(new[] {shippingRule.Object});
 
    Order order = new Order(rules.Object);
    OrderLine line = new OrderLine(0, 1, 25);
    order.OrderLines.Add(line);
 
    Assert.AreEqual( line.Amount * line.Cost + shippingCost, order.CalculateTotalCost());
}

 

Last time I used TypeMock Isolator as my mocking API - I thought I'd spice things up a bit and use a different one called Moq this time. Here, I'm creating a mock of the IShippingRule interface (which I've yet to implement), setting up an expectation (using the neat lambda syntax of Moq) that basically says that "any call to CalculateCost which has an instance of an Order passed to it as the first argument, should always return the value of shippingCost (which is 10)". Next, I set up a mock for the IRules interface (which I've yet to implement, also), with an expectation that says "any call to the Find method with IShippingRule as the generic type argument should always return an array of length 1 containing the mocked IShippingRule object". Everything is then set up, so I can create an instance of my Order class, pass it the ruleset factory and excersize it by adding an orderline and calling CalculateTotalCost. Now, to pass this test we first need to make it compile by defining the interfaces we've added:

public interface IRules
{
    IEnumerable<TRuleType> Find<TRuleType>();
}
 
public interface IShippingRule
{
    decimal CalculateCost(Order order);
}

 

Secondly, we need to refactor the Order class to accept the dependency injection:

public class Order
{
    private readonly IRules _rules;
    private readonly List<OrderLine> _orderLines = new List<OrderLine>();
 
    public Order(IRules rules)
    {
        _rules = rules;
    }
 
    public IList<OrderLine> OrderLines { get { return _orderLines; } }
 
    public decimal CalculateTotalCost()
    {
        decimal sum = this.OrderLines.Sum(line => line.Amount * line.Cost);
 
        if(null != _rules)
        {
            foreach (IShippingRule rule in _rules.Find<IShippingRule>())
            {
                sum += rule.CalculateCost(this);
            }
        }
 
        return sum;
    }
}

 

Notice now, that we've completely implemented the Order class and satisfied ourselves that it works properly without having to implement any specific shipping rules or the ruleset factory to configure them. These can later be implemented and tested by themselves, and then injected into our production code as needed. And thats it, we've now got something akin in functionality to the original example - but with a slightly different implementation caused by our test-first approach; most notably we've sacrificed a bit of the simplicity to gain a more loosely coupled implementation. As explained last time, we could use a tool like TypeMock to test our original implementation, but that coupled our test much more tightly to the implementation than the testing we've been doing in this post. In the end, I'm not really sure which way to go I prefer - there are pros and cons of both that are hard to weigh up to see a clear advantage either way. I suspect there might be some golden middle road, as usual... One thing is for sure, though, there's a lot to learn here, and whichever way you choose to do it, testing will ultimately be a reward in and of itself.

Managing Dependencies

I mentioned earlier that we've introduced a level of complexity to our design by making the dependency on our ruleset factory (IRules) explicit through constructor injection. When we start getting a lot of dependencies like this, things will quickly get complicated. To solve this, we need an Inversion of Control container. Such a container takes on the job of managing and injecting the dependencies for us, so that when we want to create an instance of our Order class, we don't have to worry about the IRules instance it requires - the container will take care of it for us. There are lots of IoC container implementations out there to choose from; one which I'm really digging at the moment is Autofac. Here's how we might use it to build a container that can manage the IRules dependency for us:

var builder = new ContainerBuilder();
 
// register the ruleset factory as a singleton
builder.Register<SqlRuleSet>().As<IRules>();
 
// register the Order factory
builder.Register<Order>().FactoryScoped();
 
using(IContainer container = builder.Build())
{
    // create an order instance - the IRules dependency is 
    //automatically resolved for us by the container
    Order order = container.Resolve<Order>();
}

 

That's a quick teaser on managing dependencies using an IoC container - look out for a more in-depth post soon!

Be the first to rate this post

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Comments

Powered by BlogEngine.NET 1.4.5.0

Welcome!

My name is Fredrik Kalseth, and this is my blog - thanks for visiting! I am fortunate enough to work with what I love for a living, and this blog is essentially the biproduct of that.

I work as a senior consultant for Capgemini, and am also an active participant in the Norwegian .NET community, as an avid attendee but also as a speaker (most recently at NNUG and MSDN Live).

As a developer, I have a wide circle of interest. My primary passion is for agile, test-driven development, with focus on best practices and clean code. That said, I also love to work on the frontend, especially with web development.

On Twitter? My handle is fkalseth. On LinkedIn? I`m there too.

NDC 2010

The conference to attend this summer happens June 16th-18th in Oslo, Norway. Are you going? Be sure to catch my talk on AOP while you're there!

 

Disclaimer

This is a personal blog; any opinions expressed here are my own and do not necessarily reflect those of my employer. All content herein is my own original creation, and as such is protected by copyright law. Unless otherwise stated, all source code posted on this blog is freely usable under the Microsoft Permissive License.

What Readers Talk About

Comment RSS