Write simpler tests with Type Builders and AutoFixture

Write simpler tests with Type Builders and AutoFixture

Writing tests for services with lots of collaborators can be tedious. I know. You will end up with complex Arrange parts and lots of fakes. Let’s see three alternatives to write simpler tests with builder methods, Type Builders and AutoFixture.

To write simpler tests for services with lots of collaborators, use builder methods to create only the fakes needed in every test. As alternative, use auto-mocking with custom classes or libraries like AutoFixture to create a service with its collaborators replaced by fakes or test doubles using a mocking library.

To show these three alternatives, let’s bring back our OrderService class. We used it to show the difference between stubs and mocks. Again, the OrderService checks if an item has stock available to then charge a credit card.

This time, let’s add a IDeliveryService to create a shipment order and a IOrderRepository to keep track of an order status. With these two changes, our OrderService will look like this:

public class OrderService
{
    private readonly IPaymentGateway _paymentGateway;
    private readonly IStockService _stockService;
    private readonly IDeliveryService _deliveryService;
    private readonly IOrderRepository _orderRepository;

    public OrderService(IPaymentGateway paymentGateway,
                        IStockService stockService,
                        IDeliveryService deliveryService,
                        IOrderRepository orderRepository)
    {
        _paymentGateway = paymentGateway;
        _stockService = stockService;
        _deliveryService = deliveryService;
        _orderRepository = orderRepository;
    }

    public OrderResult PlaceOrder(Order order)
    {
        if (!_stockService.IsStockAvailable(order))
        {
            throw new OutOfStockException();
        }

        // Process payment, ship items and store order status

        return new PlaceOrderResult(order);
    }
}

Let’s write a test to check if the payment gateway is called when we place an order. We’re using Moq to write fakes. This test will look like this:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace WithoutAnyBuilders
{
    [TestClass]
    public class OrderServiceTestsBefore
    {
        [TestMethod]
        public void PlaceOrder_ItemInStock_CallsPaymentGateway()
        {
            var stockService = new Mock<IStockService>();
            stockService.Setup(t => t.IsStockAvailable(It.IsAny<Order>()))
                        .Returns(true);
            var paymentGateway = new Mock<IPaymentGateway>();
            var deliveryService = new Mock<IDeliveryService>();
            var orderRepository = new Mock<IOrderRepository>();
            var service = new OrderService(paymentGateway.Object,
                                           stockService.Object,
                                           deliveryService.Object,
                                           orderRepository.Object);

            var order = new Order();
            service.PlaceOrder(order);

            paymentGateway.Verify(t => t.ProcessPayment(It.IsAny<Order>()));
        }
    }
}

Sometimes, we need to create fakes for our collaborators even when the tested behavior doesn’t need them.

Builder methods

One easy alternative to write simpler test is to use builder methods.

With a builder method, we only create the fakes we need inside our tests. And, inside the builder method, we create “empty” fakes for the collaborators we don’t need for the tested scenario.

We’ve used this idea of builder methods to make our tests less noisy.

Our test with a builder method looks like this:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace WithBuilderMethod
{
    [TestClass]
    public class OrderServiceTestsBuilder
    {
        [TestMethod]
        public void PlaceOrder_ItemInStock_CallsPaymentGateway()
        {
            var stockService = new Mock<IStockService>();
            stockService.Setup(t => t.IsStockAvailable(It.IsAny<Order>()))
                        .Returns(true);
            var paymentGateway = new Mock<IPaymentGateway>();
            // We add a new MakeOrderService method
            var orderService = MakeOrderService(stockService.Object, paymentGateway.Object);

            var order = new Order();
            orderService.PlaceOrder(order);

            paymentGateway.Verify(t => t.ProcessPayment(order));
        }

        // Notice we only pass the fakes we need
        private OrderService MakeOrderService(IStockService stockService, IPaymentGateway paymentGateway)
        {
            var deliveryService = new Mock<IDeliveryService>();
            var orderRepository = new Mock<IOrderRepository>();
            var service = new OrderService(paymentGateway,
                                            stockService,
                                            deliveryService.Object,
                                            orderRepository.Object);

            return service;
        }
    }
}

With the MakeOrderService() method, we only deal with the mocks we care about in every test.

Men at work
Let's use builders to write simpler tests. Photo by Ricardo Gomez Angel on Unsplash

Auto-mocking with TypeBuilder

Builder methods are fine. But, we can use an special builder to create testable services with all its collaborators replaced by fakes or test doubles. This way, we don’t need to create builder methods for every combination of services we need inside our tests.

Let me introduce you, TypeBuilder. This is a helper class I’ve been using in one of my client’s projects to create services inside our unit tests.

This TypeBuilder class uses reflection to find all the parameters in the constructor of the service to build. And, it uses Moq to build fakes for each parameter.

TypeBuilder expects a single constructor. But, you can easily extend it to pick the one with more parameters.

public class TypeBuilder<T>
{
    private readonly Dictionary<Type, object> _instances = new Dictionary<Type, object>();
    private readonly Dictionary<Type, Mock> _mocks = new Dictionary<Type, Mock>();

    public T Build()
    {
        Type type = typeof(T);
        ConstructorInfo ctor = type.GetConstructors().First();
        ParameterInfo[] parameters = ctor.GetParameters();

        var args = new List<object>();
        foreach (var param in parameters)
        {
            Type paramType = param.ParameterType;

            object arg = null;

            if (_mocks.ContainsKey(paramType))
            {
                arg = _mocks[paramType].Object;
            }
            else if (_instances.ContainsKey(paramType))
            {
                arg = _instances[paramType];
            }

            if (arg == null)
            {
                if (!_mocks.ContainsKey(paramType))
                {
                    Type mockType = typeof(Mock<>).MakeGenericType(paramType);
                    ConstructorInfo mockCtor = mockType.GetConstructors().First();
                    var mock = mockCtor.Invoke(null) as Mock;

                    _mocks.Add(paramType, mock);
                }

                arg = _mocks[paramType].Object;
            }

            args.Add(arg);
        }

        return (T)ctor.Invoke(args.ToArray());
    }

    public TypeBuilder<T> WithInstance<U>(U instance, bool force = false) where U : class
    {
        if (instance != null || force)
        {
            _instances[typeof(U)] = instance;
        }

        return this;
    }

    public TypeBuilder<T> WithMock<U>(Action<Mock<U>> mockExpression) where U : class
    {
        if (mockExpression != null)
        {
            var mock = Mock<U>();
            mockExpression(mock);

            _mocks[typeof(U)] = mock;
        }

        return this;
    }

    public Mock<U> Mock<U>(object[] args = null, bool createInstance = true) where U : class
    {
        if (!_mocks.TryGetValue(typeof(U), out var result) && createInstance)
        {
            result = args != null
                ? new Mock<U>(args)
                : new Mock<U>();

            _mocks[typeof(U)] = result;
        }

        return (Mock<U>)result;
    }
}

Let’s rewrite our sample test to use the TypeBuilder class.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace WithTypeBuilder
{
    [TestClass]
    public class OrderServiceTestsTypeBuilder
    {
        [TestMethod]
        public void PlaceOrder_ItemInStock_CallsPaymentGateway()
        {
            // 1. Create a builder
            var typeBuilder = new TypeBuilder<OrderService>();
            // 2. Configure a IStockService fake with Moq
            typeBuilder.WithMock<IStockService>(mock =>
            {
              mock.Setup(t => t.IsStockAvailable(It.IsAny<Order>()))
                .Returns(true);
            });
            // 3. Build a OrderService instance
            var service = typeBuilder.Build();

            var order = new Order();
            service.PlaceOrder(order);

            // Retrieve a fake from the builder
            typeBuilder.Mock<IPaymentGateway>()
                  .Verify(t => t.ProcessPayment(It.IsAny<Order>()));
          }
    }
}

This is what happened. First, we create a builder with var typeBuilder = new TypeBuilder<OrderService>();.

Then, to register a custom fake, we used the method WithMock<T>(). And inside it, we configured the behavior of the fake.

In our case, we created a fake StockService that returns true for any order. We did that in these lines:

typeBuilder.WithMock<IStockService>(mock =>
{
    mock.Setup(t => t.IsStockAvailable(It.IsAny<Order>()))
        .Returns(true);
});

After that, with the method Build() we got an instance of the OrderService class with fakes for all its parameters. But, the fake for IStockService has the behavior we added in the previous step.

Finally, in the Assert part, we retrieved a fake from the builder with Mock<T>(). We use it to verify if the payment gateway was called or not. We did this here:

typeBuilder.Mock<IPaymentGateway>()
            .Verify(t => t.ProcessPayment(It.IsAny<Order>()));

This TypeBuilder class comes handy to avoid creating builders manually for every service in our unit tests.

Did you notice in our example that we didn’t have to write fakes for all collaborators? We only did it for the IStockService. The TypeBuilder took care of the other fakes.

Auto-mocking with AutoFixture

If you prefer a more battle-tested solution, let’s replace our TypeBuilder with AutoFixture.

What AutoFixture does

From its docs, AutoFixture “is a tool designed to make Test-Driven Development more productive and unit tests more refactoring-safe”.

AutoFixture creates test data for us. It helps us to simplify the Arrange parts of our tests.

To start using AutoFixture, let’s install its NuGet package AutoFixture.

For example, we can create orders inside our tests with:

Fixture fixture = new Fixture();
fixture.Create<Order>();

AutoFixture will initialize all properties of an object to random values. Optionally, we can hardcode our own values if we want to.

AutoMoq

AutoFixture has integrations with mocking libraries like Moq to create services with all its parameters replaced by fakes. To use this integrations, let’s install the NuGet package AutoFixture.AutoMoq.

Let’s rewrite our sample test, this time to use AutoFixture with AutoMoq. It will look like this:

using AutoFixture;
using AutoFixture.AutoMoq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace WithAutoFixture
{
    [TestClass]
    public class OrderServiceTestsAutoFixture
    {
        // 1. Create a field for AutoFixture
        private readonly IFixture Fixture = new Fixture()
                            .Customize(new AutoMoqCustomization());

        [TestMethod]
        public void PlaceOrder_ItemInStock_CallsPaymentGateway()
        {
            // 2. Use Freeze to create a custom fake
            var stockedService = Fixture.Freeze<Mock<IStockService>>();
            stockedService.Setup(t => t.IsStockAvailable(It.IsAny<Order>()))
                          .Returns(true);
            var paymentGateway = Fixture.Freeze<Mock<IPaymentGateway>>();
            var service = Fixture.Create<OrderService>();

            var order = new Order();
            service.PlaceOrder(order);

            paymentGateway.Verify(t => t.ProcessPayment(order));
        }
    }
}

Notice this time, we used a field in our test to hold a reference to AutoFixture Fixture class. Also, we needed to add the AutoMoqCustomization behavior to make AutoFixture a type builder.

To retrieve a fake reference, we used the Freeze() method. We used these references to plug the custom behavior for the IStockService fake and to verify the IPaymentGateway fake.

Voilà! That’s how we can use a TypeBuilder helper class and AutoFixture to simplify the Arrange parts of our tests. If you prefer a simple solution, use the TypeBuilder class. But, if you don’t mind adding an external reference to your tests, use AutoFixture. Maybe, you can use it to create test data too.

If you want to know what fakes and mocks are, check What are fakes in unit testing: mocks vs stubs and learn these 5 tips to write better stubs and mocks. Are you new to mocking libraries? Read my post on how to write fakes with Moq.

Happy testing!