Autofixture Generate Specific Format Values By Extending AutoDataAttribute

Using custom specimen builders with AutoDataAttribute functionality to reduce the amount of repeated code in your Arrange phase of unit tests.

Published on 18 August 2020

Last time out we looked at how to write a custom specimen builder to generate a specific format of an identifier to fit with business requirements.

In this post I am going to show how we can extend that functionality to write less code but still harness the power of the bespoke specimen builder.

This post will build on the information in the previous post so I recommend reading it if you've not already.

Creating your POCO with a Fixture

In the previous post we were generating our poco from an AutoFixture Fixture which had the custom specimen buider added to it's Customizations list. This allows for the ExternalId property on the POCO to have a custom format.

// Arrange
var fixture = new Fixture();
fixture.Customizations.Add(new PersonExternalIdGenerator());

// Act
var sut = fixture.Create<Person>();

The issue with the above is we have to create a new fixture each test. We have to add the specimen builder each test and then ask for a POCO instance to be generated on each test.

Can we not make this easier?

DataAttribute to the Rescue

Not only does xUnit allow for simple tests to be marked as [Fact] but it also has the [Theory] attribute which tells the runner that additional data items are coming in. This allows other attributes to be decorated onto the test method and generated method parameters to be passed in.

Out of the box Autofixture has an AutoDataAttribute implementation with derives from the xUnit DataAttribute and allows for you to request arbitary instances to be passed in.

[Theory]
[AutoData]
public void Pass_Me_Stuff(string str, int i, Person person)
{

}

The above will generate a random string, a random int and an instance of our Person POCO. If you debug into the above the default conventions will be followed and the person instance will have randomly generated string property value. This breaks our requirements.

Extend AutoDataAttribute

To allow for our requirement to be met we need access to the Fixture instance which is being used to generate the test method parameters. To do this we need to make our own attribute. To save reinventing the wheel we can derive from the AutoDataAttribute which is used in the above and update how the Fixture instance is generated.

public class PersonDataAttribute : AutoDataAttribute
{
    public PersonDataAttribute() :
        base(() =>
        {
            var fixture = new Fixture();
            fixture.Customizations.Add(new PersonExternalIdGenerator());
            return fixture;
        })
    {
    }
}

The above generation of the Fixture instance should look familiar to our original unit test setup.

Using the PersonDataAttribute

As with the AutoDataAttribute example the new PersonDataAttribute is added to the test method.

AutoData attribute example

As you can see from the above the ExternalId property of the injected Person instance now follows the requirements specified by the business.

We can now re-write the original unit test as the follows.

[Theory]
[PersonData]
public void ExternalId_ShouldBe_Correct_Format_Auto(Person sut)
{
    // Assert
    sut.ExternalId.Should().StartWith("123-").And.HaveLength(7);
    var part = sut.ExternalId.Split('-')[1];
    int.TryParse(part, out int value).Should().BeTrue();
    value.Should().BeInRange(100, 999);
}

Conclusion

In this post we have reviewed the generation of specific property values. Using the power of the AutoDataAttribute and TheoryAttribute we have taken code which would have to be written per test to now be done automatically. This allows developers to generate the poco instances required for testing their functionality and not spend time writing their own "Arrange" section to generate new poco instances.

Any questions/comments then please contact me on Twitter @WestDiscGolf