Let's refactor a test: Payment report

Let’s refactor a test to follow our unit testing best practices. This test is based on a real test I had to modify in one of my client’s projects.

Imagine this test belongs to a payment gateway. The system takes payments on behalf of partners. At the end of every month, the partners get the collected payments discounting any fees. This process is called a payout.

Partners can generate a report with all the transactions associated with a payout. This report can show dates at a different timezone if the user wants it.

This is the test we’re going to refactor. This test is for the GetPayoutDetailsAsync() method. This method finds the payouts in a date range. Then, it shows all transactions related to those payouts. This method feeds a report in Microsoft Excel or any other spreadsheet software.

[TestMethod]
public async Task GetPayoutDetailsAsync_HappyPath_SuccessWithoutTimezone()
{
    var account = TestAccount;

    var payouts = TestPayouts;
    var balanceTransactions = TestBalanceTransactions;
    var payments = TestPayments;

    var request = new PayoutRequest
    {
        PageSize = 10,
        AccountId = "AnyAccountId",
        DateRange = new DateRange
        {
            StartDate = DateTime.Now.AddDays(-15),
            EndDate = DateTime.Now
        }
    };

    var builder = new TypeBuilder<PayoutDetailsService>()
        .WithAccount(account)
        .WithPayouts(payouts)
        .WithBalanceTransactions(balanceTransactions)
        .WithPayments(payments);

    var service = builder.Build();

    var result = await service.GetPayoutDetailsAsync(request);

    Assert.IsTrue(result.Any());

    builder.GetMock<IAccountService>().Verify(s => s.GetAsync(It.IsAny<string>()), Times.Once);
    builder.GetMock<IPayoutWrapper>()
        .Verify(s => s.GetPayoutsByDateRangeAsync(It.IsAny<string>(), It.IsAny<DateRange>()), Times.Once);
    builder.GetMock<IBalanceTransactionWrapper>()
        .Verify(
            s => s.GetBalanceTransactionsByPayoutsAsync(It.IsAny<string>(), It.IsAny<string>(),
                It.IsAny<CancellationToken>()), Times.Once);
}

This test uses automocking with TypeBuilder. This TypeBuilder<T> creates an instance of a class with its dependencies replaced by fakes using Moq.

Also, this test uses the Builder pattern to create fakes with some test values before building a new instance. This test relies on object mothers to create input values for the stubs.

1. Separate the Arrange/Act/Assert parts

Let’s start by grouping related code to follow the Arrange/Act/Assert (AAA) principle.

To achieve this, let’s declare variables near their first use and inline the ones used in a single place.

[TestMethod]
public async Task GetPayoutDetailsAsync_HappyPath_SuccessWithoutTimezone()
{
    // Notice we inlined all input variables
    var builder = new TypeBuilder<PayoutDetailsService>()
        .WithAccount(TestAccount)
        .WithPayouts(TestPayouts)
        .WithBalanceTransactions(TestBalanceTransactions)
        .WithPayments(TestPayments);
    var service = builder.Build();

    // Notice we moved the request variable near its first use
    var request = new PayoutRequest
    {
        PageSize = 10,
        AccountId = "AnyAccountId",
        DateRange = new DateRange
        {
            StartDate = DateTime.Now.AddDays(-15),
            EndDate = DateTime.Now
        }
    };
    var result = await service.GetPayoutDetailsAsync(request);

    Assert.IsTrue(result.Any());

    builder.GetMock<IAccountService>().Verify(s => s.GetAsync(It.IsAny<string>()), Times.Once);
    builder.GetMock<IPayoutWrapper>()
        .Verify(s => s.GetPayoutsByDateRangeAsync(It.IsAny<string>(), It.IsAny<DateRange>()), Times.Once);
    builder.GetMock<IBalanceTransactionWrapper>()
        .Verify(
            s => s.GetBalanceTransactionsByPayoutsAsync(It.IsAny<string>(), It.IsAny<string>(),
                It.IsAny<CancellationToken>()), Times.Once);
}

Notice we inlined all input variables and move the request variable closer to the GetPayoutDetailsAsync() method where it’s used.

Remember, declare variables near their first use.

2. Show the scenario under test and the expected result

Now, let’s look at the test name.

[TestMethod]
public async Task GetPayoutDetailsAsync_HappyPath_SuccessWithoutTimezone()
{
    // Notice the test name...
    // The rest of the test remains the same,
    // but not for too long
}

It states the GetPayoutDetailsAsync() method should work without a timezone. That’s the scenario of our test.

Let’s follow the “UnitOfWork_Scenario_ExpectedResult” naming convention to show the scenario under test in the middle part of the test name.

Also, let’s avoid the filler word “Success”. In this test, success means the method returns the details without showing the transactions in another timezone. We learned to avoid filler words in our test names when we learned the 4 common mistakes when writing tests.

Let’s rename our test.

[TestMethod]
public async Task GetPayoutDetailsAsync_NoTimeZone_ReturnsDetails()
{
    // Test body remains the same...
}

After this refactoring, it’s a good idea to add another test passing a timezone and checking that the found transactions are in the same timezone.

3. Make test value obvious

In the previous refactor, we renamed our test to show it works without a timezone.

Anyone reading this test should expect a variable named timezone assigned to null or a method WithoutTimeZone() in a builder. Let’s make the test value explicit.

[TestMethod]
public async Task GetPayoutDetailsAsync_NoTimeZone_ReturnsDetails()
{
    var builder = new TypeBuilder<PayoutDetailsService>()
        .WithAccount(TestAccount)
        .WithPayouts(TestPayouts)
        .WithBalanceTransactions(TestBalanceTransactions)
        .WithPayments(TestPayments);
    var service = builder.Build();

    var request = new PayoutRequest
    {
        PageSize = 10,
        AccountId = "AnyAccountId",
        DateRange = new DateRange
        {
            StartDate = DateTime.Now.AddDays(-15),
            EndDate = DateTime.Now
        },
        // Notice we explicitly set no timezone
        TimeZone = null
        //        ^^^^^
    };
    var result = await service.GetPayoutDetailsAsync(request);

    Assert.IsTrue(result.Any());

    builder.GetMock<IAccountService>().Verify(s => s.GetAsync(It.IsAny<string>()), Times.Once);
    builder.GetMock<IPayoutWrapper>()
        .Verify(s => s.GetPayoutsByDateRangeAsync(It.IsAny<string>(), It.IsAny<DateRange>()), Times.Once);
    builder.GetMock<IBalanceTransactionWrapper>()
        .Verify(
            s => s.GetBalanceTransactionsByPayoutsAsync(It.IsAny<string>(), It.IsAny<string>(),
                It.IsAny<CancellationToken>()), Times.Once);
}

If we have more than one test without a timezone, we can use a constant NoTimeZome or an object mother for the PayoutRequest, something like NoTimeZonePayoutRequest.

4. Remove over-specification

For our last refactor, let’s remove those Verify() calls. We don’t need them. We don’t need to assert on stubs.

If any of the stubs weren’t in place, probably we will get a NullReferenceException somewhere in our code. Those extra verifications make our test harder to maintain.

[TestMethod]
public async Task GetPayoutDetailsAsync_NoTimeZone_ReturnsDetails()
{
    var builder = new TypeBuilder<PayoutDetailsService>()
        .WithAccount(TestAccount)
        .WithPayouts(TestPayouts)
        .WithBalanceTransactions(TestBalanceTransactions)
        .WithPayments(TestPayments);
    var service = builder.Build();

    var request = new PayoutRequest
    {
        PageSize = 10,
        AccountId = "AnyAccountId",
        DateRange = new DateRange
        {
            StartDate = DateTime.Now.AddDays(-15),
            EndDate = DateTime.Now
        },
        TimeZone = null
    };
    var result = await service.GetPayoutDetailsAsync(request);

    Assert.IsTrue(result.Any());
    // We stopped verifying on stubs
}

Voilà! That looks better! Unit tests got our back when changing our code. It’s better to keep them clean too. They are our safety net.

If you’re new to unit testing, read Unit Testing 101, 4 common mistakes when writing your first tests and 4 test naming conventions.

For more advanced content on unit testing, check what are fakes in unit testing and don’t miss the rest of my Unit Testing 101 series.

Happy testing!

How to write better assertions in your tests

There’s a lot to say about how to write good unit tests. This time, let’s focus on best practices to write better assertions on our tests.

Here you have 5 tips to write better assertions on your unit tests.

TL;DR

  1. Follow the Arrange/Act/Assert (AAA) pattern
  2. Separate each A of the AAA pattern with line breaks
  3. Don’t put logic in your assertions
  4. Have a single Act and Assert parts in each test
  5. Use the right Assertion methods

1. Follow the Arrange/Act/Assert (AAA) pattern

If you could take home only one thing: follow the Arrange/Act/Assert (AAA) pattern.

The Arrange/Act/Assert (AAA) pattern states that each test should contain three parts: Arrange, Act and Assert.

In the Arrange part, we create classes and input values needed to call the entry point of the code under test.

In the Act part, we call the method to trigger the logic being tested.

In the Assert part, we verify the code under test did what we expected. We check if it returned the right value, threw an exception, or called another component.

For example, let’s bring back one test for Stringie, a (fictional) library to manipulate strings, to show the AAA pattern. Notice how each test has these 3 parts.

For the sake of the example, we have put comments in each AAA part. You don’t need to do that on your own tests.

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Stringie.UnitTests;

[TestClass]
public class RemoveTests
{
    [TestMethod]
    public void Remove_ASubstring_RemovesThatSubstring()
    {
        // Arrange
        string str = "Hello, world!";

        // Act
        string transformed = str.Remove("Hello");

        // Assert
        Assert.AreEqual(", world!", transformed);
    }

    [TestMethod]
    public void Remove_NoParameters_ReturnsEmpty()
    {
        // Arrange
        string str = "Hello, world!";

        // Arrange
        string transformed = str.Remove();

        // Arrange
        Assert.AreEqual(0, transformed.Length);
    }
}

ICYMI, we’ve been using Stringie to write our first unit tests in C# and to learn about 4 common mistakes when writing tests.

2. Separate each A of the AAA pattern

Use line breaks to visually separate the AAA parts of each test.

Let’s take a look at the previous tests without line breaks between each AAA part.

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Stringie.UnitTests;

[TestClass]
public class RemoveTests
{
    [TestMethod]
    public void Remove_ASubstring_RemovesThatSubstring()
    {
        string str = "Hello, world!";
        string transformed = str.Remove("Hello");
        Assert.AreEqual(", world!", transformed);
    }

    [TestMethod]
    public void Remove_NoParameters_ReturnsEmpty()
    {
        string str = "Hello, world!";
        string transformed = str.Remove();
        Assert.AreEqual(0, transformed.Length);
    }
}

Not that bad. The larger the tests, the harder it gets.

Have the three AAA parts separated to make your tests easier to read.

In case you’re wondering about those weird method names, they follow one of the most common test naming conventions.

Three paths on the snow
Separate each A of the AAA pattern. Photo by Anne Nygård on Unsplash

3. Don’t put logic in your assertions

Don’t repeat the logic under test in your assertions. And, please, don’t copy the tested logic and paste it into private methods in your test files to use it in your assertions. That’s the most common mistake when writing tests.

Use known, pre-calculated values instead. Declare constants for common expected values.

[TestMethod]
public void Remove_ASubstring_RemovesThatSubstringFromTheEnd()
{
    string str = "Hello, world! Again, Hello";

    string transformed = str.Remove("Hello").From(The.End);

    Assert.AreEqual("Hello, world! Again,", transformed);
    //              ^^^^^
    // Notice how we hardcode an expected value here
}

Notice how we hardcoded an expected value in the Assert part. We didn’t use any other method or copy the logic under test to find the expected substring.

4. Have a single Act and Assert

Have a single Act and Assert parts in your tests. Use parameterized tests to test the same scenario with different test values.

And, don’t put the test values inside an array to loop through it to then assert on each value.

This is a parameterized test with MSTest.

[DataTestMethod]
[DataRow("Hello")]
[DataRow("HELLO")]
[DataRow("HeLlo")]
public void Remove_SubstringWithDifferentCase_RemovesSubstring(string substringToRemove)
{
    var str = "Hello, world!";

    var transformed = str.RemoveAll(substringToRemove).IgnoringCase();

    Assert.AreEqual(", world!", transformed);
}

5. Use the right Assertion methods

Use the right assertion methods of your testing framework. And, don’t roll your own assertion framework.

For example, prefer Assert.IsNull(result); over Assert.AreEqual(null, result);. And, prefer Assert.IsTrue(result) over Assert.AreEqual(true, result);.

When working with strings, prefer StringAssert methods like Contains(), StartsWith() and Matches() instead of exactly comparing two strings. That would make your tests easier to maintain.

Voilà! These are 5 tips to write better assertions. If you want a more complete list of best practices to write your unit tests, check my post Unit Testing Best Practices: A Checklist. And, don’t miss how to write custom assertions to write even more readable tests.

If you’re new to unit testing, start reading Unit Testing 101 and 4 common unit testing mistakes. For more advanced tips, check how to write good unit tests and always write failing tests.

And don’t miss the rest of my Unit Testing 101 series where I cover more subjects like this one.

Happy testing!

Unit Testing Best Practices: A checklist

As part of this series on unit testing, we’ve covered a lot of subjects. From how to write your first unit tests to create test data with Builders to how to write better fakes. I hope I’ve helped you to start writing unit tests or write even better unit tests.

This time, I’m bringing some tips and best practices from my previous posts in one single place for quick reference.

1. On Naming

Choose a naming convention and stick to it.

Every test name should tell the scenario under test and the expected result. Don’t worry about long test names. But don’t name your tests: Test1, Test2, and so on.

Describe in your test names what you’re testing in a language easy to understand, even for non-programmers.

Don’t prefix your test names with “Test.” If you’re using a testing framework that doesn’t need keywords in your test names, don’t do that. With MSTest, there are attributes like [TestClass] and [TestMethod] to mark methods as tests. Other testing frameworks have similar ones.

Don’t use filler words like “Success” or “IsCorrect” in test names. Instead, tell what “success” and “correct” mean for that test. Is it a successful test because it doesn’t throw exceptions? Is it successful because it returns a value different from null? Make your test names easy to understand.

2. On Organization

Make your tests easy to find.

Put your unit tests in a test project named after the project they test. Use the suffix “Tests” or “UnitTests.” For example, if you have a library called MyLibrary, name your test project: MyLibrary.UnitTests.

Put your unit tests separated in files named after the unit of work or entry point of the code you’re testing. Use the suffix “Tests”. For a class MyClass, name your test file: MyClassTests.

Workbench full of tools
Keep your tests organized and easy to find. Photo by Barn Images on Unsplash

3. On Assertions

Follow the Arrange/Act/Assert (AAA) principle.

Separate the body of your tests. Use line breaks to visually separate the three AAA parts in the body of your tests.

Don’t repeat the logic under test in your assertions. And, please, don’t copy the tested logic and paste it into private methods in your test files to use it in your assertions. Use known or pre-calculated values, instead.

Don’t make private methods public to test them. Test private methods when calling your code under test through its public methods.

Have a single Act and Assert parts in your tests. Don’t put test values inside a collection to loop through it and assert on each one. Use parameterized tests to test the same scenario with different test values.

Use the right assertion methods of your testing framework. For example, use Assert.IsNull(result); instead of Assert.AreEqual(null, result);.

Prefer assertion methods for strings like Contains(), StartsWith() and Matches() instead of exactly comparing two strings.

4. On Test Data

Keep the amount of details at the right level

Give enough details to your readers, but not too many to make your tests noisy.

Use factory methods to reduce complex Arrange scenarios.

Make your scenario under test and test values extremely obvious. Don’t make developers decode your tests. Create constants for common test data and expected values.

Use object mothers to create input test values. Have a factory method or property holding a ready-to-use input object. Then, change what you need to match the scenario under test.

Prefer Builders to create complex object graphs. Object mothers are fine if you don’t have lots of variations of the object being constructed. If that’s the case, use the Builder pattern. Compose builders to create complex objects in your tests.

5. On Stubs and Mocks

Write dumb fakes

Use fakes when you depend on external systems you don’t control. Check your code makes the right calls with the right messages.

Avoid complex logic inside your fakes. Don’t add flags to your stubs to return one value or another. Write separate stubs instead.

Don’t write assertions for stubs. Assert on the output of your code under test or use mocks. Remember there’s a difference between stubs and mocks.

Keep one mock per test. Don’t use multiple mocks per test. Write separate tests instead.

Make tests set their own values for fakes. Avoid magic values inside your stubs.

Use descriptive names in your fakes. Name your stubs to indicate the value they return or the exception they throw. For example, ItemOutOfStockStockService and FixedDateClock.

Voilà! Those are my best practices for writing better great unit tests. Don’t forget to always start writing failing tests. And make sure they fail for the right reasons. If you don’t follow Test-Driven Development, comment out some of your code under test or change the assertions on purpose to see your tests failing.

We don’t ship our tests to end users. But it doesn’t mean we shouldn’t care about the quality of our tests. Unit tests got our back when changing our code. They’re our safety net.

Don’t miss the rest of my Unit Testing 101 series where I cover all these subjects in depth.

Want to write readable and maintainable unit tests in C#? Join my course Mastering C# Unit Testing with Real-world Examples on Udemy and learn unit testing best practices while refactoring real unit tests from my past projects. No more tests for a Calculator class.

Happy testing!

Write simpler tests with Type Builders and AutoFixture

Writing tests for services with lots of collaborators can be tedious. I know! We 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 an alternative, use auto-mocking to create a service with its collaborators replaced by test doubles.

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 an IDeliveryService to create a shipment order and an IOrderRepository to keep track of 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);
    }
}

I know, I know! We could argue our OrderService is doing a lot of things! Bear with me.

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 behavior under test doesn’t need them.

1. Builder methods

One easy alternative to writing simpler tests 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 used this idea of builder methods to write better tests by making our tests less noisy and more readable.

Our test with a builder method looks like this:

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

namespace WithABuilderMethod;

[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>();
        var orderService = MakeOrderService(stockService.Object, paymentGateway.Object);
        //                 ^^^^^
        // We add a new MakeOrderService method

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

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

    private OrderService MakeOrderService(IStockService stockService, IPaymentGateway paymentGateway)
    //                   ^^^^^
    // Notice we only pass the fakes we need
    {
        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 our test: the ones for IStockService and IPaymentService.

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

2. Auto-mocking with TypeBuilder

Builder methods are fine. But, we can use a 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 to 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, we 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 an 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 used 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 in 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.

3. 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 these 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()
    {
        var stockedService = Fixture.Freeze<Mock<IStockService>>();
        //                   ^^^^^
        // 2. Use Freeze to create a custom fake
        stockedService.Setup(t => t.IsStockAvailable(It.IsAny<Order>()))
                      .Returns(true);
        var paymentGateway = Fixture.Freeze<Mock<IPaymentGateway>>();
        var service = Fixture.Create<OrderService>();
        //            ^^^^^
        // 3. Use Create to grab an auto-mocked instance

        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<T>() 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. And, don’t miss the rest of my Unit Testing 101 series where I cover more subjects like this one.

Want to write readable and maintainable unit tests in C#? Join my course Mastering C# Unit Testing with Real-world Examples on Udemy and learn unit testing best practices while refactoring real unit tests from my past projects. No more tests for a Calculator class.

Happy testing!

Five tips for better stubs and mocks in C#

Last time, we covered what fakes are in unit testing and the types of fakes. We wrote two tests for an OrderService to show the difference between stubs and mocks.

In case you missed it, fakes are like test “simulators.” They replace external dependencies with testable components. Stubs and mocks are two types of fakes. Stubs are “simulators” that provide values or exceptions. And mocks are “simulators” that record method calls.

Before we start with the tips, let’s bring back the OrderService class from our last post.

public class OrderService 
{
    private readonly IPaymentGateway _paymentGateway;
    private readonly IStockService _stockService;

    public OrderService(IPaymentGateway paymentGateway, IStockService stockService)
    {
        _paymentGateway = paymentGateway;
        _stockService = stockService;
    }

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

        _paymentGateway.ProcessPayment(order);

        return new PlaceOrderResult(order);
    }
}

In our last post, we chose certain names for our fakes like AlwaysAvailableStockService and FixedDateClock. Let’s see why those names and what to do and not to do when working with fakes.

TL;DR

  • Don’t assert on stubs
  • Keep one mock per test
  • Avoid logic inside your fakes
  • Make tests set their own values for fakes
  • Name your fakes properly

1. Don’t assert on stubs

Let’s remember that stubs are there to provide values indirectly to our code under test. We make fakes return a value or throw an exception.

Don’t write assertions for stubs. We don’t need them. Let’s assert on the result of our tests or use mocks.

Please, don’t do this.

[TestClass]
public class OrderServiceTests
{
    [TestMethod]
    public void PlaceOrder_ItemInStock_CallsPaymentGateway()
    {
        var paymentGateway = new FakePaymentGateway();
        var stockService = new AlwaysAvailableStockService();
        var service = new OrderService(paymentGateway, stockService);

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

        Assert.IsTrue(paymentGateway.WasCalled);
        // We don't need this assertion
        Assert.IsTrue(stockService.IsStockAvailable(order));
        // ^^^^^
    }
}

In this test, let’s notice this assertion,

Assert.IsTrue(stockService.IsStockAvailable(order));

It’s redundant. It will never fail because we wrote the fake to always return true. We can get rid of it!

If we use a mocking library like Moq to write our fakes and if we forget to set up our stubs, we will get a NullReferenceException. Our code expects some values that the stubs didn’t provide. With that exception thrown, we will have a failing test.

If we write assertions for our stubs, we’re testing the mocking library, not our code.

2. Keep one mock per test

In the same spirit of keeping a single assertion per test, let’s keep one mock per test. Let’s have small and well-named tests.

Let’s say that in our OrderService, we need to log every request we made to charge a credit card and we add an ILogger<AccountService> to our service.

Please, don’t write tests with more than one mock. Like this one,

[TestClass]
public class OrderServiceTests
{
    [TestMethod]
    public void PlaceOrder_ItemInStock_CallsPaymentGatewayAndLog()
    {
        var logger = new FakeLogger();
        var paymentGateway = new FakePaymentGateway();
        var stockService = new AlwaysAvailableStockService();
        var service = new OrderService(logger, paymentGateway, stockService);

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

        Assert.IsTrue(paymentGateway.WasCalled);
        Assert.IsTrue(logger.WasCalled);
        // ^^^^^
        // Let's keep one mock per test
    }
}

Don’t use multiple mocks per test. Let’s write separate tests, instead.

And, when testing logging and logging messages, asserting on the logging message isn’t a good idea.

Cockpit of Airbus A330-200
Stubs and mocks are like test "simulators". Photo by Andrés Dallimonti on Unsplash

3. Avoid logic inside your fakes

Write dumb fakes. Let’s avoid complex logic inside fakes.

For example, let’s not add flags to our stubs to return one value or another. Let’s write separate fakes, instead.

Let’s test the OrderService with and without stock. As a counterexample, let’s use a single FakeStockService with a flag to signal the two scenarios.

[TestClass]
public class OrderServiceTests
{
    [TestMethod]
    public void PlaceOrder_ItemInStock_CallsPaymentGateway()
    {
        var paymentGateway = new FakePaymentGateway();
        // Make the stock service have stock
        var stockService = new FakeStockService
        {
            ItemInStock = true
            // ^^^^^
        };
        var service = new OrderService(paymentGateway, stockService);

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

        Assert.IsTrue(paymentGateway.WasCalled);
    }

    [TestMethod]
    public void PlaceOrder_ItemOutOfStock_ThrowsException()
    {
        var paymentGateway = new FakePaymentGateway();
        // Make the stock service have NO stock
        var stockService = new FakeStockService
        {
            ItemInStock = false
            // ^^^^^
        };
        var service = new OrderService(paymentGateway, stockService);

        var order = new Order();

        Assert.ThrowsException<OutOfStockException>(() => service.PlaceOrder(order));
    }
}

Our FakeStockService would look like this,

public class FakeStockService : IStockService
{
    public bool ItemInStock { get; set; }
    
    public bool IsStockAvailable(Order order)
    {
        return ItemInStock;
    }
}

Here our fake service uses a bool, but it could start getting more complex if we need to support more test scenarios.

Let’s not use a single fake for both scenarios. Instead, let’s write two separate fakes and make each test use a different one. Let’s name these two fakes: ItemInStockStockService and ItemOutOfStockStockService. Inside them, we always return true and false, respectively.

[TestClass]
public class OrderServiceTests
{
    [TestMethod]
    public void PlaceOrder_ItemInStock_CallsPaymentGateway()
    {
        var paymentGateway = new FakePaymentGateway();
        // One fake for the "in stock" scenario
        var stockService = new ItemInStockStockService();
        //                     ^^^^^
        var service = new OrderService(paymentGateway, stockService);

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

        Assert.IsTrue(paymentGateway.WasCalled);
    }

    [TestMethod]
    public void PlaceOrder_ItemOutOfStock_ThrowsException()
    {
        var paymentGateway = new FakePaymentGateway();
        // Another fake for the "out of stock" scenario
        var stockService = new ItemOutOfStockStockService();
        //                     ^^^^^
        var service = new OrderService(paymentGateway, stockService);

        var order = new Order();
        Assert.ThrowsException<OutOfStockException>(() => service.PlaceOrder(order));
    }
}

Don’t worry about creating lots of fakes. Fakes are cheap. Any decent IDE can create a class implementing an interface or an abstract class with a few clicks or a single keyboard shortcut.

4. Make tests set their own values for fakes

Avoid magic values in your stubs. Let’s make tests pass their own values instead of having hard-coded values in stubs.

Let’s say that StockService returns the units available instead of a simple true or false. Check this test,

[TestMethod]
public void PlaceOrder_NotEnoughStock_ThrowsException()
{
    var paymentGateway = new FakePaymentGateway();
    var stockService = new FakeStockService();
    var service = new OrderService(paymentGateway, stockService);

    var order = new Order
    {
        Quantity = 2
    };
    Assert.ThrowsException<OutOfStockException>(() => service.PlaceOrder(order));
}

Why should it throw? Why is that Quantity = 2 there? Because we buried somewhere in the FakeStockService not enough stock. Something like this,

public class FakeStockService : IStockService
{
    public int StockAvailable(Order order)
    {
        return 1;
    }
}

Instead, let the test set its own faked value,

[TestMethod]
public void PlaceOrder_NoEnoughStock_ThrowsException()
{
    var paymentGateway = new FakePaymentGateway();
    var stockService = new FakeStockService
    {
        UnitsAvailable = 1
        // ^^^^^
    };
    var service = new OrderService(paymentGateway, stockService);

    var order = new Order
    {
        Quantity = 2
        // ^^^^^
    };
    Assert.ThrowsException<OutOfStockException>(() => service.PlaceOrder(order));
}

It makes more sense! There’s only 1 unit available and we’re placing an order for 2 items. Let’s make tests fake their own values.

5. Name your fakes properly

Again for our last tip, let’s talk about names. Naming is hard!

Name stubs to indicate the value they return or the exception they throw.

We named our fake stock provider AlwaysAvailableStockService to show it always returns stock available. It obvious from its name what is the return value.

When we needed two stock providers to test the OrderService without stock, we named our fakes: ItemInStockStockService and ItemOutOfStockStockService.

Also, do you remember why we named our fake FixedDateClock? No? You can tell it by its name. It returns a fixed date, the DateTime you pass to it.

Voilà! Those are five tips to write better stubs and mocks. Remember, write dumb fakes. Don’t put too much logic in them. Let the tests fake their own values.

If you want to start using a mocking library, read my post on how to write fakes with Moq. If you find yourself writing lots of fakes for a single component, check automocking with TypeBuilder and AutoFixture. And don’t miss the rest of my Unit Testing 101 series where I cover more subjects like this one.

Happy testing!