Convenient member data sources with xUnit

In xUnit test cases can be parameterized using the attribute [InlineData] much like [Datarow] in MsTest. That is, instead of stating a [Fact], you can create a [Theory] and parameterize it, much like this example:

public int Add(int x, int y) => x + y;

public void TestAdd(int x, int y, int expected)
    int result = Add(x, y);
    Assert.Equal(expected, result);

Here we created four testcases using a general parameterized theory. This allows you to rapidly gain coverage without the boilerplate of creating a lot of test methods; with tempting copy/pasting.

However values must be constants, which limits your flexibility using this approach.

Alternatively, using [MemberData] (and [ClassData]) you can create complex data sources for your theories; something like this:

public static IEnumerable<object[]> TestAddDataSource => new List<object[]>()
    new object[] {1, 2, 3},
    new object[] {...}

We would, however, like to avoid the object-array initialization new object[] {...; and we can actually get away with this, using this simple method:

static object[] Row(params object[] os) => os;

But it actually adds a bit more flexibility, say for instance, allowing for type-conversions where needed.

In a recent project in a test for a code analysis tool, in this example, doing signs-analysis, we did a test for for example the minus operator. In the analysis, values are Integer-values, Top (too much information), or Bottom (no information at all) about a value. Basically, we have concrete information about a value or abstract information about a value. A test-theory for the full set of values would either test concrete values or that the abstractions are the same. This could could look like this:

public void TestDsMinus(Value x, Value y, Value expected)
    Value result = DsMinus(x, y);

    if (expected is Integer exi && result is Integer resultInt)
        Assert.Equal(exi.Value, resultInt.Value);
    } else
        Assert.Equal(expected.GetType(), res.GetType());


The TestMinusData would, however, have to look like this:

public static IEnumerable<object[]> TestMinusData => new List<object[]>()
    Row(new Integer(0), new Integer(1), new Integer(-1)),
    Row(Value.Bottom,   Value.Bottom,   Value.Bottom),
    Row(new Integer(1), new Integer(1), Value.Top)

Although somewhat better, this is still hard to read. With a small change to row, we can make our TestMinusData-method easier to write and to read:

public static IEnumerable<object[]> TestMinusData => new List<object[]>()
    Row(Value.Bottom, 1, Value.Bottom),
    Row(1, Value.Bottom, Value.Bottom),
    Row(Value.Bottom, Value.Bottom, Value.Bottom)

Value.Bottom and Value.Top are actually instances of class Bottom and Top respectively, both subclasses of Value. But even if ints were implicitly converted to the Integer-class, it wouldn’t do so automatically; in the actual example, Value, is an interface. We would have to cast or wrap each int, introducing a lot of unwanted noise. Instead, the Row could be altered as below, or specific row methods could be added:

static object[] Row(params object[] os) => 
  os.Select(v =>(v is int i) ? new Integer(i) : v)

In the above we convert wrap all ints in Integer, and leave everything else as is. With relatively small changes, we now write


instead of

new object[]{ new Integer(0), new Integer(1), new Integer(-1)}

Tests should be simple to read, write and understand. A technique like Row might someday make your life easier.

Using TheoryData is another great way to make good tests in XUnit. Read about it here.

Leave a Comment