Creating parameterised tests in xUnit with [InlineData], [ClassData], and [MemberData]

In this post I provide an introduction to creating parmeterised tests using xUnit's [Theory] tests, and how you can pass data into your test methods. I'll cover the common [InlineData] attribute, and also the [ClassData] and [MemberData] attributes. In the next post, I'll show how to load data in other ways by creating your own [DataAttribute].

If you're new to testing with xUnit, I suggest reading the getting started documentation. This shows how to get started testing .NET Core projects with xUnit, and provides an introduction to [Fact] and [Theory] tests.

The Add method takes two numbers, adds them together and returns the result.

We'll start by creating our first xUnit test for this class. In xUnit, the most basic test method is a public parameterless method decorated with the [Fact] attribute. The following example tests that when we pass the values 1 and 2 to the Add() function, it returns 3:

If you run your test project using dotnet test (or Visual Studio's Test Explorer), then you'll see a single test listed, which shows the test was passed:

We know that the Calculator.Add() function is working correctly for these specific values, but we'll clearly need to test more values than just 1 and 2. The question is, what's the best way to achieve this? We could copy and paste the test and just change the specific values used for each one, but that's a bit messy. Instead, xUnit provides the [Theory] attribute for this situation.

Using the [Theory] attribute to create parameterised tests with [InlineData]

In contrast, the [Theory] attribute denotes a parameterised test that is true for a subset of data. That data can be supplied in a number of ways, but the most common is with an [InlineData] attribute.

The following example shows how you could rewrite the previous CanAdd test method to use the [Theory] attribute, and add some extra values to test:

Instead of specifying the values to add (value1 and value2) in the test body, we pass those values as parameters to the test. We also pass in the expected result of the calculation, to use in the Assert.Equal() call.

The data is provided by the [InlineData] attribute. Each instance of [InlineData] will create a separate execution of the CanAddTheory method. The values passed in the constructor of [InlineData] are used as the parameters for the method - the order of the parameters in the attribute matches the order in which they're supplied to the method.

Tip: The xUnit 2.3.0 NuGet package includes some Roslyn analyzers that can help ensure that your [InlineData] parameters match the method's parameters. The image below shows three errors: not enough parameters, too many parameters, and parameters of the wrong type

If you run the tests for this method, you'll see each [InlineData] creates a separate instance. xUnit handily adds the parameter names and values to the test description, so you can easily see which iteration failed.

As an aside, do you see what I did with that int.MinValue test? You're testing your edge cases work as expected right? 😉

The [InlineData] attribute is great when your method parameters are constants, and you don't have too many cases to test. If that's not the case, then you might want to look at one of the other ways to provide data to your [Theory] methods.

Using a dedicated data class with [ClassData]

If the values you need to pass to your [Theory] test aren't constants, then you can use an alternative attribute, [ClassData], to provide the parameters. This attribute takes a Type which xUnit will use to obtain the data:

We've specified a type of CalculatorTestData in the [ClassData] attribute. This class must implement IEnumerable<object[]>, where each item returned is an array of objects to use as the method parameters. We could rewrite the data from the [InlineData] attribute using this approach:

Obviously you could write this enumerator in multiple ways, but I went for a simple iterator approach. xUnit will call .ToList() on your provided class before it runs any of the theory method instances, so it's important the data is all independent. You don't want to have shared objects between tests runs causing weird bugs!

The [ClassData] attribute is a convenient way of removing clutter from your test files, but what if you don't want to create an extra class? For these situations, you can use the [MemberData] attribute.

Using generator properties with the [MemberData] properties

The [MemberData] attribute can be used to fetch data for a [Theory] from a static property or method of a type. This attribute has quite a lot options, so I'll just run through some of them here.

Loading data from a property on the test class

The [MemberData] attribute can load data from an IEnnumerable<object[]> property on the test class. The xUnit analyzers will pick up any issues with your configuration, such as missing properties, or using properties that return invalid types.

In the following example I've added a Data property which returns an IEnumerable<object[]>, just like for the [ClassData]

Loading data from a method on the test class

As well as properties, you can obtain [MemberData] from a static method. These methods can even be parameterised themselves. If that's the case, you need to supply the parameters in the [MemberData], as shown below:

In this case, xUnit first calls GetData(), passing in the parameter as numTests: 3. It then uses each object[] returned by the method to execute the [Theory] test.

Loading data from a property or method on a different class

This option is sort of a hybrid between the [ClassData] attribute and the [MemberData] attribute usage you've seen so far. Instead of loading data from a property or method on the test class, you load data from a property or method on some other specified type:

That pretty much covers your options for providing data to [Theory] tests. If these attributes don't let you provide data in the way you want, you can always create your own, as you'll see in my next post.