How to combine PropertyData and AutoNSubstituteData attributes in xunit/autofixture?

折月煮酒 提交于 2019-11-30 21:26:00
Nikos Baxevanis

There are two ways to do this:

Option 1 - Using AutoFixture.Xunit and the CompositeDataAttribute class:

internal class AutoNSubstituteDataAttribute : AutoDataAttribute
{
    internal AutoNSubstituteDataAttribute()
        : base(new Fixture().Customize(new AutoNSubstituteCustomization()))
    {
    }
}

internal class AutoNSubstitutePropertyDataAttribute : CompositeDataAttribute
{
    internal AutoNSubstitutePropertyDataAttribute(string propertyName)
        : base(
            new DataAttribute[] { 
                new PropertyDataAttribute(propertyName), 
                new AutoNSubstituteDataAttribute() })
    {
    }
}

Define the test cases as below:

public class Scenario
{
    public static IEnumerable<object[]> InvalidInvariantCase1
    {
        get
        {
            yield return new string[] { null };
        }
    }

    public static IEnumerable<object[]> InvalidInvariantCase2
    {
        get
        {
            yield return new string[] { string.Empty };
        }
    }

    public static IEnumerable<object[]> InvalidInvariantCase3
    {
        get
        {
            yield return new string[] { " " };
        }
    }
}

Then declare the parameterized test as:

public class Scenarios
{
    [Theory]
    [AutoNSubstitutePropertyData("InvalidInvariantCase1")]
    [AutoNSubstitutePropertyData("InvalidInvariantCase2")]
    [AutoNSubstitutePropertyData("InvalidInvariantCase3")]
    public void AParameterizedTest(
        string invalidConnString,
        IDeal deal,
        IDbConnection conn,
        IDb db,
        ISender sender,
        string query)
    {
    }
}

Please note that the parameterized parameter invalidConnString have to be declared before the other parameters.

Option 2 - Using Exude:

public class Scenario
{
    public void AParameterizedTest(
        IDeal deal,
        IDbConnection conn,
        IDb db,
        ISender sender,
        string invalidConnString,
        string query)
    {
    }

    [FirstClassTests]
    public static TestCase<Scenario>[] RunAParameterizedTest()
    {
        var testCases = new [] 
        {
            new 
            {
                invalidConnString = (string)null
            },
            new
            {
                invalidConnString = string.Empty
            },
            new
            {
                invalidConnString = " "
            }
        };

        var fixture = new Fixture()
            .Customize(new AutoNSubstituteCustomization());

        return testCases
            .Select(tc =>
                new TestCase<Scenario>(
                    s => s.AParameterizedTest(
                        fixture.Create<IDeal>(),
                        fixture.Create<IDbConnection>(),
                        fixture.Create<IDb>(),
                        fixture.Create<ISender>(),
                        tc.invalidConnString,
                        fixture.Create<string>())))
            .ToArray();
    }
}
Mark Seemann

The [Theory] attribute works by looking for one or more 'data source attributes'; for example

  • [InlineData]
  • [PropertyData]
  • [ClassData]
  • etc.

The [AutoData] attribute is just another such attribute, as is your derived [AutoNSubstituteData] attribute.

It's possible to add more than one 'data source attribute' to the same [Theory], as witnessed by the idiomatic use of the [InlineData] attribute:

[Theory]
[InlineData("foo")]
[InlineData("bar")]
[InlineData("baz")]
public void MyTest(string text)

This produces three test cases.

It's also possible to combine [PropertyData] and [AutoData], but it probably doesn't do what you want it to do. This:

[Theory]
[AutoNSubstituteData]
[PropertyData("InvalidInvariant")]
public void MyTest(/* parameters go here */)

will result in 1 + n test cases:

  • 1 test case from [AutoNSubstituteData]
  • n test cases from the InvalidInvariant property

These two attributes know nothing about each other, so you can't combine them in the sense that they're aware of each other.

However, when you're implementing a property, you can write whatever code you'd like, including using a Fixture instance, so why not just do this?

public static IEnumerable<string[]> InvalidInvariant
{
    get
    {
        var fixture = new Fixture().Customize(new MyConventions());
        // use fixture to yield values...,
        // using the occasional hard-coded test value
    }
}

Another option is to use derive from the InlineAutoDataAttribute, which would enable you to write your test cases like this:

[Theory]
[MyInlineAutoData("foo")]
[MyInlineAutoData("bar")]
[MyInlineAutoData("baz")]
public void MyTest(string text, string someOtherText, int number, Guid id)

This would cause the first argument (text) to be populated with the constants from the attributes, while the remaining parameters are populated by AutoFixture.

Theoretically, you may also be able to combine the [AutoData] and [PropertyData] attributes using the CompositeDataAttribute, but it may not work the way you'd like.

Finally, you could consider using Exude for true first-class parameterized tests.

AlexFoxGill

I have implemented an AutoPropertyDataAttribute that combines xUnit's PropertyDataAttribute with AutoFixture's AutoDataAttribute. I posted it as an answer here.

In your case you will need to inherit from the attribute in the same way as you would from an AutoDataAttribute, with the exception that you pass a fixture creation function instead of an instance:

public class AutoNSubPropertyDataAttribute : AutoPropertyDataAttribute
{
    public AutoNSubPropertyDataAttribute(string propertyName)
        : base(propertyName, () => new Fixture().Customize(new AutoNSubstituteCustomization()))
    {
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!