Keep your unit tests DRY with AutoFixture Customizations

15/12/2011

When I first incorporated AutoFixture as part of my daily unit testing workflow, I noticed how a consistent usage pattern had started to emerge.
This pattern can be roughly summarized in three steps:

  1. Initialize an instance of the Fixture class.
  2. Configure the way different types of objects involved in the test should be created by using the Build<T> method.
  3. Create the actual objects with the CreateAnonymous<T> or CreateMany<T> methods.

As a result, my unit tests had started to look a lot like this:

[Test]
public void WhenGettingAListOfPublishedPostsThenItShouldOnlyIncludeThose()
{
    // Step 1: Initialize the Fixture
    var fixture = new Fixture();

    // Step 2: Configure the object creation
    var draft = fixture.Build<Post>()
        .With(a => a.IsDraft = true)
        .CreateAnonymous();
    var publishedPost = fixture.Build<Post>()
        .With(a => a.IsDraft = false)
        .CreateAnonymous();
    fixture.Register<IEnumerable<Post>>(() => new[] { draft, publishedPost });

    // Step 3: Create the anonymous objects
    var posts = fixture.CreateMany<Post>();

   // Act and Assert...
}

In this particular configuration, AutoFixture will satisfy all requests for IEnumerable<Post> types by returning the same array with exactly two Post objects: one with the IsDraft property set to True and one with the same property set to False.

At that point I felt pretty satisfied with the way things were shaping up: I had managed to replace entire blocks of boring object initialization code with a couple of calls to the AutoFixture API, my unit tests were getting smaller and all was good.

Duplication creeps in

After a while though, the configuration lines created in Step 2 started to repeat themselves across multiple unit tests. This was naturally due to the fact that different unit tests sometimes shared a common set of object states in their test scenario. Things weren’t so DRY anymore and suddenly it wasn’t uncommon to find code like this in the test suite:

[Test]
public void WhenGettingAListOfPublishedPostsThenItShouldOnlyIncludeThose()
{
    var fixture = new Fixture();
    var draft = fixture.Build<Post>()
        .With(a => a.IsDraft = true)
        .CreateAnonymous();
    var publishedPost = fixture.Build<Post>()
        .With(a => a.IsDraft = false)
        .CreateAnonymous();
    fixture.Register<IEnumerable<Post>>(() => new[] { draft, publishedPost });
    var posts = fixture.CreateMany<Post>();

    // Act and Assert...
}

[Test]
public void WhenGettingAListOfDraftsThenItShouldOnlyIncludeThose()
{
    var fixture = new Fixture();
    var draft = fixture.Build<Post>()
        .With(a => a.IsDraft = true)
        .CreateAnonymous();
    var publishedPost = fixture.Build<Post>()
        .With(a => a.IsDraft = false)
        .CreateAnonymous();
    fixture.Register<IEnumerable<Post>>(() => new[] { draft, publishedPost });
    var posts = fixture.CreateMany<Post>();

    // Different Act and Assert...
}

See how these two tests share the same initial state even though they verify completely different behaviors? Such blatant duplication in the test code is a problem, since it inhibits the ability to make changes.
Luckily a solution was just around the corner as I discovered customizations.

Customizing your way out

A customization is a pretty general term. However, put in the context of AutoFixture it assumes a specific definition:

A customization is a group of settings that, when applied to a given Fixture, control the way AutoFixture will create anonymous instances of the types requested through that Fixture.

What that means is that I could take all the boilerplate configuration code produced during Step 2 and move it out of my unit tests into a single place, that is a customization. That allowed me to specify only once how different objects needed to be created for a given scenario, and reuse that across multiple tests.

public class MixedDraftsAndPublishedPostsCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        var draft = fixture.Build<Post>()
            .With(a => a.IsDraft = true)
            .CreateAnonymous();
        var publishedPost = fixture.Build<Post>()
            .With(a => a.IsDraft = false)
            .CreateAnonymous();
        fixture.Register<IEnumerable<Post>>(() => new[] { draft, publishedPost });
    }
}

As you can see, ICustomization is nothing more than a role interface that describes how a Fixture should be set up. In order to apply a customization to a specific Fixture instance, you’ll simply have to call the Fixture.Customize(ICustomization) method, like shown in the example below.
This newly won encapsulation allowed me to rewrite my unit tests in a much more terse way:

[Test]
public void WhenGettingAListOfDraftsThenItShouldOnlyIncludeThose()
{
    // Step 1: Initialize the Fixture
    var fixture = new Fixture();

    // Step 2: Apply the customization for the test scenario
    fixture.Customize(new MixedDraftsAndPublishedPostsCustomization());

    // Step 3: Create the anonymous objects
    var posts = fixture.CreateMany<Post>();

    // Act and Assert...
}

The configuration logic now exists only in one place, namely a class whose name clearly describes the kind of test data it will produce.
If applied consistently, this approach will in time build up a library of customizations, each representative of a given situation or scenario. Assuming that they are created at the proper level of granularity, these customizations could even be composed to form more complex scenarios.

Conclusion

Customizations in AutoFixture are a pretty powerful concept in of themselves, but they become even more effective when mapped directly to test scenarios. In fact, they represent a natural place to specify which objects are involved in a given scenario and the state they are supposed to be in. You can use them to remove duplication in your test code and, in time, build up a library of self-documenting modules, which describe the different contexts in which the system’s behavior is being verified.

Advertisement

3 Responses to “Keep your unit tests DRY with AutoFixture Customizations”

  1. Petar Repac Says:

    Hi !

    My thoughts:

    Seems that with .CreateAnonymous() you introduce randomness into your tests. This is not beneficial IMO. Your tests could randomly succeed and fail.

    By introducing customizations you introduced another level of abstraction (new method) and your tests are not self contained and are less readable. I personally don’t try to DRY my tests. This way they are completely independent.

    Didn’t use AutoFixture yet.

    Regards, Petar


    • Hi Petar,

      Thanks for your comment. You bring up a couple of interesting points that I’d like to address here.

      AutoFixture is a library that helps you generate anonymous test data to use in your tests. The term “anonymous” indicates that the actual value of the test data doesn’t matter to the test itself, as long as it’s valid within the constraints of the test scenario. The purpose of AutoFixture is to automatically provide random data within the specified constraints, so that the developer doesn’t have to. Consider this test of an hypothetical Calculator class:

      [Test]
      public void WhenAddingTwoIntegersThenItShouldReturnTheirSum()
      {
          var fixture = new Fixture();
          var firstOperand = fixture.CreateAnonymous<int>();
          var secondOperand = fixture.CreateAnonymous<int>();
          var sut = new Calculator();
      
          var result = sut.Add(firstOperand, secondOperand);
      
          Assert.AreEqual(firstOperand + secondOperand, result);
      }
      

      The only constraint put on the test data is that the two operands should be integers. Their actual value doesn’t have any effect on the outcome of the test thus they shouldn’t be part of the test itself.
      This kind of “controlled randomness” is actually beneficial to the tests, since it proves that the verified behaviors work with more than some fixed sets of constants.

      Regarding your point that unit tests should self-contained, I actually used to follow that principle myself. However I moved away from it since I found that the duplication of “setup” code in the unit tests was impairing the ability improve the code base, simply because of the sheer number of tests that had to be changed after a refactoring.
      In general keeping the tests DRY makes them more flexible in the face of change, while choosing good self-explanatory names helps making sure that they’re readable.

      • Petar Repac Says:

        Thank you for giving me an intro in this concept.
        As for non-DRY tests you have a point in cases of large number of test.

        As for anonymous test data I will keep that in mind.

        Regards, Petar


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.