logo

Composite xUnit.net Data Attributes

xUnit.net extensions support data-driven tests called Theories. Such tests are similar to regular xUnit.net tests but instead of being decorated with [Fact] they are decorated with [Theory].

Below is a data-driven test with the data coming a Microsoft Excel (.xls) spreadsheet.

[Theory]
[ExcelData("UnitTestData.xls", "SELECT x, y FROM Data")]
public void Foo(object x, object y)
{
	// 'x' and 'y' are values from the .xls spreadsheet.
}

Also, a data-driven test with the data coming from a type implementing the IEnumerable<object[]>.

[Theory]
[ClassData(typeof(CollectionOfSpecifiedString))]
public void Bar(object x, object y)
{
	// 'x' and 'y' are values from the IEnumerable<object[]> type.
}

internal class CollectionOfSpecifiedString : IEnumerable<object[]>
{
    public IEnumerator<object[]> GetEnumerator()
    {
        yield return new object[]
        {
            "foo", "zoo"
        };
    }

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

In the above samples, [ExcelData] and [ClassData] are attributes representing a data source for a data-driven test.

Using data from multiple attributes

Below is a data-driven test with the data coming from a type implementing the IEnumerable<object[]> combined with the data coming from an .xls spreadsheet.

[Theory]
[ClassExcelData(
    typeof(CollectionOfSpecifiedString),
    "UnitTestData.xls", "SELECT x, y FROM Data")]
public void Zoo(object x, object y)
{
	// 'x' is coming from the IEnumerable<object[]> type.
	// 'y' is coming from the .xls spreadsheet.
}

internal class CollectionOfSpecifiedString : IEnumerable<object[]>
{
    public IEnumerator<object[]> GetEnumerator()
    {
        yield return new object[]
        {
            "foo"
        };
    }

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

Creating a composite attribute

The [ClassExcelData] from the previous example is a composite of two xUnit.net’s data attributes [ClassData] and [ExcelData].

All we have to do is create a type deriving from CompositeDataAttribute, passing in its base constructor an array of the data attributes we would like to compose.

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
internal class ClassExcelDataAttribute : CompositeDataAttribute
{
    internal ClassExcelDataAttribute(Type type, string filename, string selectStatement)
        : base(new DataAttribute[] {
                new ClassDataAttribute(type),
                new ExcelDataAttribute(filename, selectStatement) })
    {
    }
}

The description for the CompositeDataAttribute algorithm can be found here.

When defining a composite data attribute, it is acceptable for the first attribute to provide some (or all) data for the parameters of the test method. However, subsequent data attributes must be able to provide the data for the exact position where the previous attribute stopped.

Obtaining the CompositeDataAttribute class

CompositeDataAttribute is currently bundled with AutoFixture extension for xUnit.net. You can use it by installing the AutoFixture.Xunit NuGet package.