How to use InlineData, MemberData and ClassData in xUnit

Published: Monday 22 September 2025

If you aren't using the Theory attributes InlineData, MemberData, or ClassData , you aren't taking full advantage of xUnit's capabilities.

Understanding when to use these attributes will make your tests more robust and maintainable.

What we are testing

We are going to test two methods. The first converts a speed from mph to kph. The other does the opposite - converts kph to mph.

// SpeedConversionHelper.cs
public static class SpeedConversionHelper
{
	public static decimal ConvertToKph(decimal mph)
	{
		return mph * 1.6093m;
	}

	public static decimal ConvertToMph(decimal kph)
	{
		return kph / 1.6093m;
	}
}

If you are only using Fact 

Fact is used for straightforward, single-scenario tests in xUnit. It has no parameters and does not depend on external data. Use the [Fact] attribute about your test method to declare your unit test with this scenario:

// SpeedConversionFactTests.cs
public class SpeedConversionFactTests
{
	[Fact]
	public void ConvertToKph_Fact_ReturnsCorrectSpeed()
	{
		var act = SpeedConversionCalculator.ConvertToKph(100);

		Assert.StrictEqual(160.93m, act);
	}

	[Fact]
	public void ConvertToMph_Fact_ReturnsCorrectSpeed()
	{
		var act = SpeedConversionCalculator.ConvertToMph(160.93m);

		Assert.StrictEqual(100, act);
	}
}

However, if you want to add multiple inputs for your unit tests, you will need something else.

Use Theory for parameterised tests

Theory is similar to Fact, but it can take parameters. This allows you to run the same test logic with multiple data sets making it perfect for data-driven testing.

There are a number of different attributes that you can use with Theory as to where you can get the data from.

Use InlineData for simple datasets

The InlineData attribute allows you to pass arguments directly into a test.

// SpeedConversionInlineDataTests.cs
public class SpeedConversionInlineDataTests
{
	[Theory]
	[InlineData(100, 160.93)]
	[InlineData(50, 80.465)]
	public void ConvertToKph_Theory_ReturnsCorrectSpeed(decimal mph, decimal expectedKph)
	{
		var act = SpeedConversionCalculator.ConvertToKph(mph);

		Assert.StrictEqual(expectedKph, act);
	}

	[Theory]
	[InlineData(321.86, 200)]
	[InlineData(241.395, 150)]
	public void ConvertToMph_Theory_ReturnsCorrectSpeed(decimal kph, decimal expectedMph)
	{
		var act = SpeedConversionCalculator.ConvertToMph(kph);

		Assert.StrictEqual(expectedMph, act);
	}
}

The way it works is that you pass parameters into the InlineData attribute and these match the parameters in the same order as the test method.

This is best for small, simple datasets. You can use the InlineData attribute multiple times for the same test, but it can clutter the tests if it's used too often.

One thing with the InlineData attribute is that the values must be hardcoded. You can't perform any sort of calculation. In addition, the values can't be used with other tests making it inflexible.

Use MemberData for pulling data from a property or method

The MemberData attribute lets you supply test data from a static property, method, or field. This should be used for larger data sets or when the data needs calculation before being passed to the test.

In this example, we've created two static methods returning a list of an object array. The object array gets translated to each of the parameters in the test method in the same order as the array index.

In the MemberData attribute, we've specified the static method by calling the nameof expression followed by the name of the static method:

// SpeedConversionHelperMemberDataObjectTests.cs
public class SpeedConversionHelperMemberDataObjectTests
{
	public static IList<object[]> MphToKphTestData =>
		new List<object[]>
		{
			new object[] { 100, 100 * 1.6093 },
			new object[] { 150, 150 * 1.6093 }
		};

	public static IList<object[]> KphToMphTestData =>
		new List<object[]>
		{
			new object[] { 200, 200 / 1.6093m },
			new object[] { 150, 150 / 1.6093m }
		};

	[Theory]
	[MemberData(nameof(MphToKphTestData))]
	public void ConvertToKph_MemberData_ReturnsCorrectSpeed(decimal mph, decimal expectedKph)
	{
		var act = SpeedConversionCalculator.ConvertToKph(mph);

		Assert.StrictEqual(expectedKph, act);
	}

	[Theory]
	[MemberData(nameof(KphToMphTestData))]
	public void ConvertToMph_MemberData_ReturnsCorrectSpeed(decimal kph, decimal expectedMph)
	{
		var act = SpeedConversionCalculator.ConvertToMph(kph);

		Assert.StrictEqual(expectedMph, act);
	}
}

Use ClassData for supplying data from a separate class

The ClassData attribute is very similar to MemberData, but the test data parameters are supplied from a class.

We've created classes for our test data which implements IEnumerable<object[]>. The data is stored in the _data field and then enumerated as part of the public GetEnumerator method.

We then use the ClassData attribute and specify the class by using the typeof expression:

// SpeedConversionHelperClassDataObjectTests.cs
public class SpeedConversionHelperClassDataObjectTests
{
	public class SpeedMphToKphDataClass : IEnumerable<object[]>
	{
		private readonly IList<object[]> _data = new List<object[]>
		{
			new object[] {100, 100 * 1.6093m},
			new object[] {200, 200 * 1.6093m}
		};

		public IEnumerator<object[]> GetEnumerator() => _data.GetEnumerator();

		IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
	}

	public class SpeedKphToMphDataClass : IEnumerable<object[]>
	{
		private readonly IList<object[]> _data = new List<object[]>
		{
			new object[] {300, 300 / 1.6093m},
			new object[] {200, 200 / 1.6093m }
		};

		public IEnumerator<object[]> GetEnumerator() => _data.GetEnumerator();

		IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
	}

	[Theory]
	[ClassData(typeof(SpeedMphToKphDataClass))]
	public void ConvertToKph_ClassData_ReturnsCorrectSpeed(decimal mph, decimal expectedKph)
	{
		var act = SpeedConversionCalculator.ConvertToKph(mph);

		Assert.StrictEqual(expectedKph, act);
	}

	[Theory]
	[ClassData(typeof(SpeedKphToMphDataClass))]
	public void ConvertToMph_ClassData_ReturnsCorrectSpeed(decimal kph, decimal expectedMph)
	{
		var act = SpeedConversionCalculator.ConvertToMph(kph);

		Assert.StrictEqual(expectedMph, act);
	}
}

Use TheoryData for strongly typed parameters

With MemberData and ClassData, if you want to your parameters to be strongly typed, you can use the TheoryData type instead of object[]. This allows you to pass generic types dependant on what parameter types you are using in your unit tests.

When using MemberData, the static method returns the TheoryData class with it's generic types. As we are using two decimals in our data sets, we would add two decimal generic types.

// SpeedConversionHelperMemberDataTheoryDataTests.cs
public class SpeedConversionHelperMemberDataTheoryDataTests
{
	public static TheoryData<decimal, decimal> MphToKphTestData =>
		new TheoryData<decimal, decimal>
		{
			{ 100, 100 * 1.6093m },
			{ 150, 150 * 1.6093m }
		};

	public static TheoryData<decimal, decimal> KphToMphTestData =>
		new TheoryData<decimal, decimal>
		{
			{ 200, 200 / 1.6093m },
			{ 150, 150 / 1.6093m }
		};

	[Theory]
	[MemberData(nameof(MphToKphTestData))]
	public void ConvertToKph_MemberData_ReturnsCorrectSpeed(decimal mph, decimal expectedKph)
	{
		var act = SpeedConversionCalculator.ConvertToKph(mph);

		Assert.StrictEqual(expectedKph, act);
	}

	[Theory]
	[MemberData(nameof(KphToMphTestData))]
	public void ConvertToMph_MemberData_ReturnsCorrectSpeed(decimal kph, decimal expectedMph)
	{
		var act = SpeedConversionCalculator.ConvertToMph(kph);

		Assert.StrictEqual(expectedMph, act);
	}
}

If you are using ClassData, the class will inherit the TheoryData class with it's generic types. When you create an instance of the class, you can call the Add method in the constructor to add the data values that you wish to add in your unit tests.

// SpeedConversionHelperClassDataTheoryDataTests.cs
public class SpeedConversionHelperClassDataTheoryDataTests
{
	public class SpeedMphToKphDataClass : TheoryData<decimal, decimal>
	{
		public SpeedMphToKphDataClass()
		{
			Add(100, 100 * 1.6093m);
			Add(200, 200 * 1.6093m);
		}
	}

	public class SpeedKphToMphDataClass : TheoryData<decimal, decimal>
	{
		public SpeedKphToMphDataClass()
		{
			Add(300, 300 / 1.6093m);
			Add(200, 200 / 1.6093m);
		}
	}

	[Theory]
	[ClassData(typeof(SpeedMphToKphDataClass))]
	public void ConvertToKph_ClassData_ReturnsCorrectSpeed(decimal mph, decimal expectedKph)
	{
		var act = SpeedConversionCalculator.ConvertToKph(mph);

		Assert.StrictEqual(expectedKph, act);
	}

	[Theory]
	[ClassData(typeof(SpeedKphToMphDataClass))]
	public void ConvertToMph_ClassData_ReturnsCorrectSpeed(decimal kph, decimal expectedMph)
	{
		var act = SpeedConversionCalculator.ConvertToMph(kph);

		Assert.StrictEqual(expectedMph, act);
	}
}

Watch the video

You'll learn about the power of using InlineData, MemberData, or ClassData when you watch this video to master these Theory attributes.

In addition, when you download the code example, you'll be able to try each of these methods out for yourself.

When to use these types

In summary, use Fact for small static tests and Theory for parameterised, data-driven tests. With Theory, use InlineData for small sets of hard coded data and either MemberData, or ClassData  if you require your data to be calculated.

Using this additional functionality will not only get you to take full advantage of xUnit's capabilities, but it will keep your tests robust and readable.