# Creating Data-Driven Tests With xUnit

> Data-driven testing is where test data comes from an external source, which is why it's also known as parameterized testing. I'm going to show you four ways to write parameterized tests with xUnit: InlineData, MemberData, ClassData, and TheoryData. And I'll tell you which one I prefer.

Published: 2023-03-18. Author: Milan Jovanović.

Canonical: https://milanjovanovic.tech/blog/creating-data-driven-tests-with-xunit

xUnit gives you four ways to feed data into a parameterized test: `InlineData`, `MemberData`, `ClassData`, and `TheoryData`.
`InlineData` takes constant values in the attribute, and the other three load the data from a member or a class.
`TheoryData` is the one that gives you type safety, since `MemberData` and `ClassData` both hand back untyped `object[]`.

**Data-driven testing** is a testing method where test data is provided through some external source.
Hence it's also known as **parameterized testing**.

A popular testing library in .NET that supports parameterized testing is **xUnit**.
It uses attributes to define test methods.
The `Fact` attribute defines a simple test, and the `Theory` attribute defines a parameterized test.

In this week's newsletter, I'm going to show you four ways to write **parameterized tests** with xUnit:

- `InlineData`
- `MemberData`
- `ClassData`
- `TheoryData`

And I'll discuss which approach I think is the best.

Let's dive in.

## Writing Parameterized Tests With InlineData

The simplest way to write parameterized tests with xUnit is using the `InlineData` attribute.
You provide test data by passing in values to the `InlineData` constructor.

Here's how that would look like:

```csharp
[Theory]
[InlineData("test@test.com", "test.com")]
[InlineData("milan@milanjovanovic.tech", "milanjovanovic.tech")]
public void EmailParser_Should_Return_Domain(string email, string expectedDomain)
{
    // Arrange
    var parser = new EmailParser();

    // Act
    var domain = parser.ParseDomain(email);

    // Assert
    Assert.Equal(domain, expectedDomain);
}
```

In this example, we provide two `string` values to the `InlineData` attribute, which represent the `email` and `expectedDomain` parameters in the test.
We can specify the `InlineData` attribute as many times as we want, to introduce more test cases.

The downside of this approach is that it becomes very verbose when we have many test cases.
And we are limited to only using constant data for the parameters.

## Writing Parameterized Tests With MemberData

With the `MemberData` attribute we have the ability to programmatically provide the test data.
You can load the test data from a static property or member of a type.

Here's an example of using the `MemberData` attribute to load test data from a property:

```csharp
[Theory]
[MemberData(nameof(EmailTestData))]
public void EmailParser_Should_Return_Domain(string email, string expectedDomain)
{
    // Arrange
    var parser = new EmailParser();

    // Act
    var domain = parser.ParseDomain(email);

    // Assert
    Assert.Equal(domain, expectedDomain);
}

public static IEnumerable<object[]> EmailTestData => new List<object>
{
    new object[] { "test@test.com", "test.com" },
    new object[] { "milan@milanjovanovic.tech", "milanjovanovic.tech" }
};
```

You specify the name of the member in the `MemberData` attribute, and it's a best practice to use the `nameof` operator
so that you can rename the property (or method) in the future without breaking your test.

The one constraint is that the property (or method) has to return `IEnumerable<object[]>`, so there is no strong typing.

## Writing Parameterized Tests With ClassData

The `ClassData` attribute allows you to extract test data into its own class.
This is helpful for organizing your test data separately from your tests, and it allows for easier reuse.
You load the test from a class the inherits from `IEnumerable<object[]>` and implements the `GetEnumerator` method.

Here's an example of using the `ClassData` attribute to load test data from a class:

```csharp
[Theory]
[ClassData(typeof(EmailTestData))]
public void EmailParser_Should_Return_Domain(string email, string expectedDomain)
{
    // Arrange
    var parser = new EmailParser();

    // Act
    var domain = parser.ParseDomain(email);

    // Assert
    Assert.Equal(domain, expectedDomain);
}

public class EmailTestData : IEnumerable<object[]>
{

    public IEnumerable<object[]> GetEnumerator()
    {
        yield return new object[] { "test@test.com", "test.com" };
        yield return new object[] { "milan@milanjovanovic.tech", "milanjovanovic.tech" };
    }

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

Unfortunately, this approach is complicated because you have to implement the `IEnumerable` interface.

It almost defeats the purpose of separating test data from the actual tests.

And we still suffer from lack of type-safety.

Is there a better solution?

## Writing Parameterized Tests With TheoryData

Let me introduce you to `TheoryData`, which is my preferred way of providing test data for parameterized tests.
Using the `TheoryData` class you can implement a class to provide test data while having the benefit of type-safety.

Here's an example of using `TheoryData` in combination with `ClassData`:

```csharp
[Theory]
[ClassData(typeof(EmailTestData))]
public void EmailParser_Should_Return_Domain(string email, string expectedDomain)
{
    // Arrange
    var parser = new EmailParser();

    // Act
    var domain = parser.ParseDomain(email);

    // Assert
    Assert.Equal(domain, expectedDomain);
}

public class EmailTestData : TheoryData<string, string>
{
    public EmailTestData()
    {
        Add("test@test.com", "test.com");
        Add("milan@milanjovanovic.tech", "milanjovanovic.tech");
    }
};
```

How does `TheoryData` work?

It's a generic class that allows us to specify the types for our parameterized test.

You just call the `Add` method in the `EmailTestData` constructor to provide test data for a single test case.
And introducing more test cases comes down to calling the `Add` method multiple times.

You can also use `TheoryData` in combination with `MemberData`, and return `TheoryData` from a property or method.

## Which Approach Should You Use?

I showed you four approaches to write parameterized tests with xUnit:

- `InlineData`
- `MemberData`
- `ClassData`
- `TheoryData`

So which one should you use?

Here's my personal preference that you can follow if you want:

- `InlineData` for simple test cases
- `TheoryData` using `ClassData` for complex test cases

Thank you for reading, and have a wonderful Saturday.

---

## Frequently asked questions

### What is the difference between Fact and Theory in xUnit?

The Fact attribute defines a simple test, while the Theory attribute defines a parameterized test, where the test data comes from an external source such as InlineData, MemberData, or ClassData.

### How does InlineData work in xUnit?

You pass constant values to the InlineData attribute, and each attribute instance becomes one test case. It is the simplest approach, but it becomes verbose with many test cases and you are limited to constant data for the parameters.

### What is the difference between MemberData and ClassData in xUnit?

MemberData loads test data programmatically from a static property or member of a type. ClassData extracts test data into its own class implementing IEnumerable<object[]>, which allows easier reuse. Both return object arrays, so neither gives you strong typing.

### What is TheoryData in xUnit?

TheoryData is a generic class that lets you specify the types for your parameterized test, giving you type safety. You inherit from it, call Add in the constructor for each test case, and reference the class through ClassData or MemberData.

### Which xUnit parameterized testing approach should you use?

A practical preference: use InlineData for simple test cases, and TheoryData combined with ClassData for complex test cases. TheoryData keeps the test data separate from the tests while avoiding the untyped object arrays of plain ClassData.
