AutoDataAttribute's concrete object creation logic calls all property getters once

Viewed 200

Using AutoFixture 3.50 and xUnit.NET, it seems that there is a difference between the way that Fixture.Create() creates concrete objects and the way that AutoData Theory tests create concrete objects.

Simple example:

public class Foo
{
    private string prop;
    public string Prop
    {
        get
        {
             if (prop == null) { prop = "Prop"; } // Breakpoint 'A'
             return prop;
        }
    }
}

Test using Fixture:

[Fact]
public void FixtureTest()
{
    var fixture = new Fixture();
    var result = fixture.Create<Foo>(); // Breakpoint 'B1'
}

Test using AutoDataAttribute:

[Theory, AutoData]
public void AutoDataTest(Foo sut)
{
    var bar = 1; // Essential no-op, Breakpoint 'B2'
}

In the former test, breakpoint 'B1' is hit while Breakpoint 'A' is never hit. In the latter test, breakpoint 'A' is hit before breakpoint 'B2' is hit. This is problematic when I have "lazily" initialized property not dissimilar from the one above - because the backing field of the property is initialized before the test is run, I cannot test the initialization logic.

Is there a way to customize AutoDataAttribute so that I can get around this behavior? Or possibly, maybe this is a bug?

1 Answers

This is, in fact, not an AutoFixture issue, but rather an xUnit.net issue, if you will. You can reproduce it entirely without AutoFixture like this:

[Theory, ClassData(typeof(FooTestCases))]
public void ClassDataTest(Foo sut)
{
    var bar = 1; // Essential no-op, Breakpoint 'B3'
}

private class FooTestCases : IEnumerable<object[]>
{
    public IEnumerator<object[]> GetEnumerator()
    {
        yield return new object[] { new Foo() };
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

If you debug into this ClassDataTest, you'll also hit breakpoint 'A' before you hit breakpoint 'B3'.

The reason is that the xUnit.net test runner wants to give you a good display name for each parametrised test, so it tries to create a readable string representation of all the arguments passed to each test case in a [Theory].

When a data object doesn't explicitly override ToString, xUnit.net falls back to reading all the properties and build up a display string from those. That's what's happening here.

I've tried searching for some official documentation of that behaviour, but the best I've been able to find is this. As suggested there, you can get around the issue by overriding ToString:

public class Foo
{
    private string prop;
    public string Prop
    {
        get
        {
            if (prop == null) { prop = "Prop"; } // Breakpoint 'A'
            return prop;
        }
    }

    public override string ToString()
    {
        return "Foo";
    }
}

This change stops breakpoint 'A' from being hit, both when using ClassData and AutoData. Whether or not you want to override ToString is another question.

If you don't want to override ToString on Foo, perhaps you can get around the issue by testing a test-specific child class that does override ToString, like this:

[Theory, AutoData]
public void AutoDataTestFoo(TestFoo sut)
{
    var bar = 1; // Essential no-op, Breakpoint 'B4'
}

public class TestFoo : Foo
{
    public override string ToString()
    {
        return "Foo";
    }
}

This also stops breakpoint 'A' from being hit.

As long as Foo isn't sealed, you should be able to do this. If Foo is sealed, I can't think of any other workaround than to write the test in the style of FixtureTest.

Related