Simplify your unit tests with auto-mocking and TypeBuilder
Tags: csharp, beginners, showdev, testing
Published: 2021-09-27 13:52:26

_I originally posted an extended version of this post [on my blog](https://canro91.github.io/2021/06/21/WriteSimplerTestsTypeBuilderAndAutoFixture/) a couple of weeks ago. It's part of a series I've been publishing, called [Unit Testing 101](https://canro91.github.io/UnitTesting)_

Writing tests for services with lots of collaborators can be tedious. I know, I know! You will end up with complex Arrange parts and lots of fakes. Let's see how to write simpler tests using auto-mocking with TypeBuilder.

**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 to create a service with its collaborators replaced by fakes or test doubles.**

To show auto-mocking, let's bring back our `OrderService` class. We used it to show the [difference between stubs and mocks](https://canro91.github.io/2021/05/24/WhatAreFakesInTesting/). 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:

```csharp
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](https://canro91.github.io/2020/08/11/HowToCreateFakesWithMoq/). This test will look like this:

```csharp
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>()));
        }
    }
}
```

<figure>
<img src="https://images.unsplash.com/photo-1512207736890-6ffed8a84e8d?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=400&ixid=MnwxfDB8MXxhbGx8fHx8fHx8fHwxNjIzNjkyODcw&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=600" alt="Men at work" />

<figcaption>Let's use builders to write simpler tests. Photo by <a href="https://unsplash.com/@ripato?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Ricardo Gomez Angel</a> on <a href="https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></figcaption>
</figure>

## Write simpler tests with 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, we create "empty" fakes for the collaborators we don't need for the tested scenario. Something like this:

```csharp
[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()` builder method, we only deal with the fake we care about in our test, `IStockService`. This new method takes care of creating the rest of fakes to comply with the `OrderService` constructor.

## Auto-mocking with TypeBuilder

Builder methods are fine. But, if we have lots of testing scenarios, we need to create builders for every combination of dependencies needed to mock in our tests.

As alternative to plain builder methods, we can use an special builder to automatically create fakes for every dependency of the tested service. That's why we call this technique auto-mocking. Well, to be precise, I would be auto-faking. You got the idea!

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.

```csharp
public class TypeBuilder<T>
{
    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
            {
                Type mockType = typeof(Mock<>).MakeGenericType(paramType);
                ConstructorInfo mockCtor = mockType.GetConstructors().First();
                var mock = mockCtor.Invoke(null) as Mock;

                _mocks.Add(paramType, mock);
            }

            args.Add(arg);
        }

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

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

        _mocks[typeof(U)] = mock;

        return this;
    }

    public Mock<U> Mock<U>() where U : class
    {
        if (!_mocks.TryGetValue(typeof(U), out var result))
        {
            result = new Mock<U>();
            
            _mocks[typeof(U)] = result;
        }

        return (Mock<U>)result;
    }
}
```

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.

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

```csharp
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);

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

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

Then, we customized the `IStockService` fake to always return `true` with the `WithMock<T>()` method. We did it in these lines:

```csharp
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:

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

Did you notice we didn't have to write fakes for every collaborator? We only did it for the `IStockService`. The `TypeBuilder` took care of creating fakes for all others.

Voilà! That's how we can use auto-mocking with a `TypeBuilder` helper class to simplify the Arrange parts of our tests. If you prefer a more robust alternative, use AutoFixture. It's a free and open source library to create test data with support for auto-mocking with Moq.

_Upgrade your unit testing skills with my course: [Mastering C# Unit Testing with Real-world Examples](https://www.udemy.com/course/mastering-csharp-unit-testing-with-real-world-examples/?referralCode=8456B1B78E2EDE923174) on Udemy. Practice with hands-on exercises and learn best practices by refactoring real-world unit tests._

_Happy testing!_