Over the past 20 years, testing has become a cornerstone of software development. To turn software development into a true profession, we need to create robust, secure, safe, and efficient systems. The simplest tool in our toolbox starts us on this road: the unit test.

Like an accountant, the unit test is a fantastic way for us to perform double-entry bookkeeping. The unit test double checks our work is correct, and makes sure we keep it from morphing into something we did not want. However, unit tests, without proper maintenance and care, can become unwieldly. We must take great care in unit tests, as much as we do with source code itself.

This article will go into two simple refactorings that will help you improve your unit tests: the builder, and the fixture. These two techniques will make your unit tests more maintainable and less brittle to source code change.

When designing a unit test, I like to use the famous Arrange, Act, Assert method. As people who use this technique may know, the arrange phase can balloon in size. With complex unit tests, the arrange phase can be 90% of your unit test. The builder and fixture patterns try and reduce this, simplifying your unit tests, and encouraging the don’t repeat yourself principle.

N.B. Examples shown will be using C# with xUnit. However, ideas can be applied to any object-oriented language.

The Builder

The builder pattern can reduce the arrange phase considerably. Let’s take an example piece of code:

This is an excerpt from the ASP.NET Core MVC GitHub repository. It show a class that has three dependencies: RazorProject, IOptions{T}, and an ILogger.

If we want to unit test the class, then we need to arrange it:

This is an example test from the ASP.NET Core repository that tests the provider class. As can be predicted, the arrange phase is the biggest part of this unit test. So how can we improve this code?

One part of this code is the arrangement of the TestRazorProject class. To make this code more readable we can introduce a TestRazorProjectBuilder:

What we have done here is taken the code that is directly in the unit test, and moved it into its own encapsulation.

There are two important points to make about this builder class. Firstly, The AddFile method is using a fluent interface syntax. This means it is returning the current instance of the builder, so calls to methods on the builder can be chained. Secondly, we are doing an implicit cast to the TestRazorProject object. These two points allow us to refactor the unit test into the following (note the implicit cast):

The arrange code is now 8 lines, down from 13. Not a massive jump, but I hope you see, it is a lot more readable, and the code inside the builder is now reusable. We could extend the builder further, and allow it to create custom directory hierarchies.

The builder pattern is just the starting point in reducing the arrange phase. The fixture can help the unit test to become even more succinct.

The Fixture

A fixture is a class which creates the system under test. In our example, the system under test is the RazorProjectPageRouteModelProvider. An example fixture for the provider could be:

In a similar way to the builder pattern, we are providing a fluent interface in order to allow chaining of method calls to the fixture. On construction, we are providing sensible defaults, but we also allow the setting of these through method calls to the fixture.

The final unit test can now look like this:

This arrange phase is now simple and succinct. This will lead to code reuse between unit tests, and an easier maintenance overhead.

The most important benefit of a fixture class, is that if we change how we instantiate the system under test, and we have 100 unit tests using the fixture, only the fixture has to change. This is easier than modifying all 100 unit tests due to adding a dependency to our system under test.

Summary

What we have learned in this article is the application of the builder and fixture patterns. The builder pattern allows us to make unit tests easier to read, and can help in instantiating complex auxiliary classes. The fixture pattern allows us to encapsulate the way we create systems under test, and therefore reduces the overhead when changes to the source code occur.

There are tools out there that can make our lives even easier in the arrange phase. A popular tool that I encourage you to investigate (and I may write a blog post for in the future) is AutoFixture. AutoFixture provides a way to remove your arrange phase completely.

For more helpful ways of refactoring your unit tests, I recommend reading xUnit Test Patterns. It is an in-depth look at how we can refactor unit tests to make them more robust and maintainable.

I hope this helps.