Search code examples
c#unit-testingxunitxunit.net

Pass complex parameters to [Theory]


Xunit has a nice feature: you can create one test with a Theory attribute and put data in InlineData attributes, and xUnit will generate many tests, and test them all.

I want to have something like this, but the parameters to my method are not 'simple data' (like string, int, double), but a list of my class:

public static void WriteReportsToMemoryStream(
    IEnumerable<MyCustomClass> listReport,
    MemoryStream ms,
    StreamWriter writer) { ... }

Solution

  • There are many xxxxData attributes in XUnit. Check out for example the MemberData attribute.

    You can implement a property that returns IEnumerable<object[]>. Each object[] that this method generates will be then "unpacked" as a parameters for a single call to your [Theory] method.

    See i.e. these examples from here

    Here are some examples, just for a quick glance.

    MemberData Example: just here at hand

    public class StringTests2
    {
        [Theory, MemberData(nameof(SplitCountData))]
        public void SplitCount(string input, int expectedCount)
        {
            var actualCount = input.Split(' ').Count();
            Assert.Equal(expectedCount, actualCount);
        }
     
        public static IEnumerable<object[]> SplitCountData => 
            new List<object[]>
            {
                new object[] { "xUnit", 1 },
                new object[] { "is fun", 2 },
                new object[] { "to test with", 3 }
            };
    }
    

    XUnit < 2.0: Another option is ClassData, which works the same, but allows to easily share the 'generators' between tests in different classes/namespaces, and also separates the 'data generators' from the actual test methods.

    ClassData Example

    public class StringTests3
    {
        [Theory, ClassData(typeof(IndexOfData))]
        public void IndexOf(string input, char letter, int expected)
        {
            var actual = input.IndexOf(letter);
            Assert.Equal(expected, actual);
        }
    }
     
    public class IndexOfData : IEnumerable<object[]>
    {
        private readonly List<object[]> _data = new List<object[]>
        {
            new object[] { "hello world", 'w', 6 },
            new object[] { "goodnight moon", 'w', -1 }
        };
     
        public IEnumerator<object[]> GetEnumerator()
        { return _data.GetEnumerator(); }
     
        IEnumerator IEnumerable.GetEnumerator()
        { return GetEnumerator(); }
    }
    

    XUnit >= 2.0: Instead of ClassData, now there's an 'overload' of [MemberData] that allows to use static members from other classes. Examples below have been updated to use it, since XUnit < 2.x is pretty ancient now. Another option is ClassData, which works the same, but allows to easily share the 'generators' between tests in different classes/namespaces, and also separates the 'data generators' from the actual test methods.

    MemberData Example: look there to another type

    public class StringTests3
    {
        [Theory, MemberData(nameof(IndexOfData.SplitCountData), MemberType = typeof(IndexOfData))]
        public void IndexOf(string input, char letter, int expected)
        {
            var actual = input.IndexOf(letter);
            Assert.Equal(expected, actual);
        }
    }
     
    public class IndexOfData : IEnumerable<object[]>
    {
        public static IEnumerable<object[]> SplitCountData => 
            new List<object[]>
            {
                new object[] { "hello world", 'w', 6 },
                new object[] { "goodnight moon", 'w', -1 }
            };
    }
    

    Disclaimer :)

    Last time checked @20210903 with dotnetfiddle.net on C# 5.0 and xunit 2.4.1 .. and failed. I couldn't mix-in a test-runner into that fiddle. But at least it compiled fine. Note that this was originally written years ago, things changed a little. I fixed them according to my hunch and comments. So.. it may contain inobvious typos, otherwise obvious bugs that would instantly pop up at runtime, and traces of milk & nuts.