DataSource and Data-driven Testing Using XUnit

If you are anything like me, you avoided data-driven tests in MSTest because they were such a pain to write and maintain. However, I know not everyone is like me and I also know that even though we try to avoid things, we do not always succeed. So, in this entry in the series on migrating from MSTest to XUnit, we will look at migrating your data-driven tests, but before we get into the details, let's briefly recap on what MSTest provides for data-driving tests; the DataSource attribute.

The DataSource attribute specifies a data source from which data points are loaded. The test can then reference the specific data row in the data source from the TestContext's DataRow property. You may recall that we touched on the jack-of-all-trades, master-of-none TestContext back in the entry on outputting from our tests. As MSDN explains, given a table of data rows like this:

FirstNumber SecondNumber Sum
0 1 1
1 1 2
2 -3 -1

DataSource is used like this:

[DataSource(@"Provider=Microsoft.SqlServerCe.Client.4.0; Data Source=C:\Data\MathsData.sdf;", "Numbers")]  
[TestMethod()]  
public void AddIntegers_FromDataSourceTest()  
{  
    var target = new Maths();  
  
    // Access the data  
    int x = Convert.ToInt32(TestContext.DataRow["FirstNumber"]);  
    int y = Convert.ToInt32(TestContext.DataRow["SecondNumber"]);   
    int expected = Convert.ToInt32(TestContext.DataRow["Sum"]);  
    int actual = target.IntegerMethod(x, y);  
    Assert.AreEqual(expected, actual,  
        "x:<{0}> y:<{1}>",  
        new object[] {x, y});  
}

I do not recall using this attribute in earnest, and though perhaps others think of it more fondly, I found it a frustration to use. Thankfully, XUnit is much more inline with my intuition1.

Data-driven test methods in XUnit are called theories and are adorned with the Theory attribute2. The Theory attribute is always accompanied by at least one data attribute which tells the test runner where to find data for the theory. There are three built-in attributes for providing data: InlineData, MemberData, and ClassData. Third-party options are also available (check out AutoFixture and its AutoData attribute) and you can create your own if you find it necessary.

InlineData provides a simple way to describe a single test point for your theory; MemberData takes the name of a static member (method, field, or property) and any arguments it might need to generate your data; and ClassData takes a type that can be instantiated to provide the data. A single test point is provided as an array of type object, and while XUnit provides the TheoryData types to allow strongly-typed declaration of test data points, fundamentally, every data source  is IEnumerable. Finally, rather than the obscure TestContext.DataRow property, data points are provided to a theory test method via the test method's arguments.

So, given all this information, the above example from MSDN could be expressed as follows:

[InlineData(0, 1, 1)]
[InlineData(1, 1, 2)]
[InlineData(2, -3, -1)]
[Theory]
public void AddIntegers_FromDataTest(int x, int y, int expected)
{
    //Arrange
    var target = new Maths();  
  
    // Act
    int actual = target.IntegerMethod(x, y);  

    // Assert
    Assert.AreEqual(expected, actual,  
        "x:<{0}> y:<{1}>",  
        new object[] {x, y});
}

I took the liberty of using the arrange/act/assert test layout as part of this rewrite as I think it enhances test readability. However, you can see from this example how much easier data-driven testing is under XUnit when compared with traditional MSTest3.

Of course, I totally skipped the fact that the MSTest example used a database for the test data source. That was deliberate to simplify the example, but if we still wanted to use a database to obtain our data points (or some other data source), we can leverage the MemberData or ClassData attributes, or even roll our own.

That brings us to the end of this post on migrating data-driven tests from MSTest to XUnit. This also brings us almost to the end of the whole series on migrating from MSTest to XUnit; if you think I missed something important, please leave a comment. Next time, we will finish up with a look at some of the bits around running XUnit tests such as parallel execution, test runners, and the like.

  1. in case you hadn't noticed, I like XUnit []
  2. instead of [Fact] as on non-data-driven tests []
  3. This approach makes so much sense that MSTest introduced it for phone and WinRT app tests and is bringing it to everyone with MSTest version 2 []