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

后端 未结 3 1667
孤城傲影
孤城傲影 2021-01-05 08:47

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

AutoFixture, xUnit.net, and Auto Mocking

I would like to combine this wit

相关标签:
3条回答
  • 2021-01-05 09:02

    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()))
        {
        }
    }
    
    0 讨论(0)
  • 2021-01-05 09:07

    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();
        }
    }
    
    0 讨论(0)
  • 2021-01-05 09:26

    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.

    0 讨论(0)
提交回复
热议问题