Click here to Skip to main content
15,884,078 members
Articles / General Programming
Tip/Trick

Fake IT: Roll Your Own Configurable Test Doubles

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
31 May 2014CPOL4 min read 7.5K   3  
Roll your own configurable test doubles

Introduction

If you are like most developers these days, then you are probably writing at least some unit tests for your projects, and if you are writing unit tests then you are likely familiar with the concept of using stubs, fakes, and mocks to stand in for code that has not been written yet or to isolate the subject under test (SUT) from its depended on components (DOCs).

There are many mature and robust mocking frameworks available like FakeItEasy, JustMock Lite, Moq, NSubstitute, and Rhino Mocks that you can employ to stand in for your DOCs. If you aren’t familiar with at least one of these frameworks, then I highly recommend taking the time; the learning curve with most of these frameworks is relatively short. I am going to assume, however, you have a reason for deciding to roll your own, and there are some good and valid reasons for doing so.

I am also going to assume you want to avoid getting bogged down into the differences between the various types of test doubles, so I am not going to discuss that here. Instead, I am just going to get on with the more pragmatic aspects and show you how to easily write configurable test doubles that can be used for both state and behavior verification as well as stand in for code that you haven’t gotten around to writing yet.

Using the Code

For a demo project, I chose a hypothetical order fulfillment application. The Order class, which will serve as our SUT is dependent on a repository that implements the interface IInventoryRepository to mediate between the domain and the datastore. In order to test our SUT in isolation, we will author a configurable test double to stand in for a real repository.

The Order class looks like this:

C#
public class Order
    {
        private List<LineItem> lineItems;

        private IInventoryRepository inventory;

        public Order(IInventoryRepository inventory)
        {
            this.lineItems = new List<LineItem>();
            this.inventory = inventory;
        }

        public bool IsCompleted { get; private set; }

        public void AddLineItem(int sku, int count)
        {
            var lineItem = this.lineItems.FirstOrDefault(li => li.SKU == sku);
            var product = this.inventory[sku];

            if (product == null) throw new
                InvalidOperationException("Product does not exist.");

            if (lineItem != null)
            {
                if(count + lineItem.Count > product.Available) throw new
                InvalidOperationException("Insufficient quantity available.");

                this.lineItems.Remove(lineItem);
                lineItem.Count += count;
            }
            else
            {
                if (count > product.Available) throw new
                InvalidOperationException("Insufficient quantity available.");

                lineItem = new LineItem() { SKU = sku, Count = count };
            }

            this.lineItems.Add(lineItem);
        }

        public void Complete()
        {
            var products = new List<Product>();
             
            this.lineItems.ForEach(li =>
            {
                var product = this.inventory[li.SKU];
                product.Decrement(li.Count);
                products.Add(product);
            });

            this.inventory.Save(products.ToArray());

            IsCompleted = true;
        }

        private class LineItem
        {
            public int SKU { get; set; }

            public int Count { get; set; }
        }
    }

The interface IInventoryRepository is very simple with only an indexer property to retrieve products and a Save method that is used to update inventory quantities when the order is completed:

C#
public interface IInventoryRepository
{
    Product this[int sku] { get; }

    void Save(params Product[] products);
}

So, let's get started!

Rolling your own test double is fairly straightforward. We could for most tests get away with rolling something static. The primary issue with static test doubles though is they are, well, static. If you wanted to write a set of tests to verify the SUT can respond appropriately to receiving both valid and invalid or unexpected inputs from a DOC, you would need to author separate sets of test doubles to provide those inputs; one set for valid inputs and another set for invalid inputs. For example, what if you wanted to make the Save method or indexer property throw some file I/O exception - something that could happen if the DOC were a real repository - to make sure the SUT handles it correctly? More test doubles mean more code, and more code - even test code - means more code to maintain. That is not what we want.

What we want are configurable test doubles. Configurable doubles will let us set expectations for each test without the need to author a new double. The way we will do this is by setting up delegates for each member that may get called during a test:

C#
class TestInventoryRepository : IInventoryRepository
{
    public TestInventoryRepository() { }

    public Action<Product[]> SaveAction { get; set; }

    public Func<int, Product> IndexerFunction { get; set; }

    public Product this[int sku]
    {
        get
        {
            return IndexerFunction(sku);
        }
    }
        
    public void Save(params Product[] products)
    {
        SaveAction(products);
    }
}

The types Action and Func are delegates. Action types are void delegates, and Func types return a value. For example, Action<Product[]> is a delegate for a method that takes an instance of a Product array as an argument and returns void. Func<int, Product> is a delegate that takes an int as an argument and returns an instance of Product.

In our unit tests, we can then set the expectations by assigning methods or anonymous methods to the delegates. The following configuration rigs our test double to capture the array of products the SUT passed in to the double’s Save method so we can verify the SUT is completing the order correctly:

C#
var savedProducts = new List<Product>();
testRepository.SaveAction = arr =>
{
    savedProducts.AddRange(arr);
};

And if we want to verify the SUT can handle the DOC throwing an exception, we just assign the SaveAction delegate the following:

C#
testRepository.SaveAction = arr =>
{
    throw new System.IO.IOException("Something bad happened.");
};

The IndexerFunction delegate can be assigned similar code to throw an exception or to return a value:

C#
testRepository.IndexerFunction = sku =>
{
    return new Product() { SKU = sku, Description = "Test Product", Count = 100 };
};

If you want to learn more about test doubles and test patterns, I highly recommend Meszaros’s book xUnit Test Patterns: Refactoring Test Code, which is the definitive guide on the matter.

If you found this tip helpful, please feel free to leave me comments. Happy testing!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
Enterprise software developer, private pilot, shooting sports enthusiast, husband, father. Not necessarily in that order.

Comments and Discussions

 
-- There are no messages in this forum --