Back to articles
2 min read

NUnit Extensibility and AutoFixture

Exploring how to bring AutoFixture's AutoData conventions to NUnit through its extensibility APIs.

Gert Jansen van Rensburg

Gert Jansen van Rensburg

Software consultant

As a software consultant we can’t always choose the libraries we use on a client site, and so the story begins.

I fell in love with a library called AutoFixture-it really simplifies the arrange phase when writing unit tests. There are many patterns on how to use AutoFixture, but the one I prefer keeps test code free of direct AutoFixture dependencies.

Mark Seemann, the author of AutoFixture, also wrote an extension for xUnit.net called AutoDataAttribute which supplies auto-generated arguments to a parameterised unit test:

[Theory, AutoData]
public void IntroductoryTest(int expectedNumber, MyClass sut)
{
    int result = sut.Echo(expectedNumber);
    Assert.Equal(expectedNumber, result);
}

Extending AutoDataAttribute allows you to customise the internal fixture that generates arguments.

public class TestConventionsAttribute : AutoDataAttribute
{
    public TestConventionsAttribute()
        : base(new Fixture().Customize(new AutoNSubstituteCustomization()))
    {
    }
}

I wanted the same experience in NUnit. After reading several blog posts and the NUnit documentation on extensibility I was more confused than when I started.

Where to begin? NUnit has a TheoryAttribute, but the implementation differs hugely from xUnit. The NUnit DataAttribute is property-only, so it wasn’t even an option to extend.

After more investigation I found TestCaseAttribute and examples of parameterised unit tests in NUnit:

[TestCase(12, 3, 4)]
[TestCase(12, 2, 6)]
[TestCase(12, 4, 3)]
public void DivideTest(int n, int d, int q)
{
    Assert.AreEqual(q, n / d);
}

This was closer to what I wanted, so I created an AutoTestCaseAttribute inheriting TestCaseAttribute. After many attempts to provide arguments dynamically I discovered the property I needed to set was internal-and intentionally so. #sadface

More reading led me to TestCaseSourceAttribute, which lets you define parameters elsewhere:

static int[] EvenNumbers = new int[] { 2, 4, 6, 8 };

[Test, TestCaseSource("EvenNumbers")]
public void TestMethod(int num)
{
    Assert.IsTrue(num % 2 == 0);
}

This looked ideal, but wasn’t meant to be. The arguments passed must be statically defined and extending the attribute hit the same issues as TestCaseAttribute.

The conclusion: I needed to write an NUnit add-in that implements ITestCaseProvider2.

public interface ITestCaseProvider
{
    bool HasTestCasesFor(MethodInfo method);
    IEnumerable GetTestCasesFor(MethodInfo method);
}

public interface ITestCaseProvider2 : ITestCaseProvider
{
    bool HasTestCasesFor(MethodInfo method, Test suite);
    IEnumerable GetTestCasesFor(MethodInfo method, Test suite);
}

Below is where the magic happens:

public class AutoTestCaseProvider : ITestCaseProvider2
{
    ...

    public bool HasTestCasesFor(MethodInfo method)
    {
        return Reflect.HasAttribute(method, AutoFixtureNUnitFramework.AutoTestCaseAttribute, false);
    }

    ...

    public IEnumerable GetTestCasesFor(MethodInfo method, Test parentSuite)
    {
        Type[] parameterTypes = method.GetParameters().Select(o => o.ParameterType).ToArray();

        ArrayList parameterList = new ArrayList();

        var attributes = Reflect.GetAttributes(method, AutoFixtureNUnitFramework.AutoTestCaseAttribute, false);

        foreach (TestCaseDataAttribute attr in attributes)
        {
            foreach (var arguments in attr.GetArguments(method, parameterTypes))
            {
                ParameterSet parms = new ParameterSet();
                parms.Arguments = arguments;

                parameterList.Add(parms);
            }
        }

        return parameterList;
    }

    ...
}

Using reflection, NUnit.Framework identifies any unit tests with AutoTestCaseAttribute applied, and calls GetArguments. That method uses the configured fixture to generate arguments for the parameterised test.

public abstract class TestCaseDataAttribute : Attribute
{
    ...

    public abstract IEnumerable<object[]> GetArguments(MethodInfo method);

    ...
}

public class AutoTestCaseAttribute : TestCaseDataAttribute
{
    ...

    public override IEnumerable<object[]> GetArguments(MethodInfo method)
    {
        if (method == null)
        {
            throw new ArgumentNullException("method");
        }

        var specimens = new List<object>();
        foreach (var p in method.GetParameters())
        {
            CustomizeFixture(p);

            var specimen = Resolve(p);
            specimens.Add(specimen);
        }

        return new[] { specimens.ToArray() };
    }

    ...
}

An example of the final result:

[TestFixture]
public class Tests
{
    [Test, AutoTestCase]
    public void IntroductoryTest(int expectedNumber, MyClass sut)
    {
        int result = sut.Echo(expectedNumber);
        Assert.Equal(expectedNumber, result);
    }
}

As I write this I have submitted a pull request and I’m awaiting a code review. Hopefully the code will be merged and set free for the world to use soon.

Comments

Share your thoughts and join the discussion below.