Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am using the [AutoNSubstituteData] attribute, which was posted here:

AutoFixture, xUnit.net, and Auto Mocking

I would like to combine this with the [PropertyData("")] attribute from xunit extensions.

This is my test:

public static IEnumerable<string[]> InvalidInvariant
{
    get
    {
        yield return new string[] { null };
        yield return new [] { string.Empty };
        yield return new [] { " " };
    }
}

[Theory, AutoNSubstituteData, PropertyData("InvalidInvariant")]
public void TestThatGuardsAreTriggeredWhenConnectionStringArgumentIsInvalid(
    IDeal deal,
    IDbConnection conn,
    IDb db,
    ISender sender,
    string invalidConnString,
    string query)
{
    deal.Init.Group.Returns(Group.A);
    deal.Aggr.Group.Returns(Group.A);
    deal.Product.Commodity.Returns(Product.Commodity.E);

    var sut = new Handler(db, sender);
    Assert.Throws<ArgumentException>(() => 
        sut.HandleDeal(deal, conn, invalidConnString, query));
}

Is there a way to combine these attributes or to get the desired functionality (mock everything, except for invalidConnstring, which should be filled with the property-data)?

like image 849
mrt181 Avatar asked Apr 10 '14 10:04

mrt181


2 Answers

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();
    }
}
like image 142
Nikos Baxevanis Avatar answered Dec 17 '22 08:12

Nikos Baxevanis


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.

like image 27
Mark Seemann Avatar answered Dec 17 '22 09:12

Mark Seemann