Skip to content

What it is

xUnit.net is the most widely used .NET testing framework, with a minimal API, parallel execution by default and constructor-based test setup.

[Fact] marks a parameterless test, [Theory] with data attributes marks a parameterised one. Each test class is instantiated fresh per test, so state cannot leak.

Installation

dotnet new xunit -n MyTests

Getting started

The smallest useful thing you can do with it, and what each part means.

Facts, theories and per-test isolation
public class SlugifyTests
{
    private readonly Slugifier _sut = new();   // fresh for every test

    [Fact]
    public void Handles_empty_input() =>
        Assert.Equal(string.Empty, _sut.Slugify(""));

    [Theory]
    [InlineData("Hello World", "hello-world")]
    [InlineData("C# & Go!", "c-go")]
    [InlineData("  spaced  ", "spaced")]
    public void Slugifies(string input, string expected) =>
        Assert.Equal(expected, _sut.Slugify(input));
}
The class is constructed once per test, which is why there is no [SetUp]. That design makes shared mutable state between tests structurally impossible.

Advanced usage

Where the library earns its place over a simpler alternative.

Shared expensive fixtures
public class DatabaseFixture : IAsyncLifetime
{
    public string ConnectionString { get; private set; } = default!;

    public async Task InitializeAsync() => ConnectionString = await StartContainerAsync();
    public async Task DisposeAsync() => await StopContainerAsync();
}

[CollectionDefinition("db")]
public class DbCollection : ICollectionFixture<DatabaseFixture> { }

[Collection("db")]   // shares one fixture across the collection
public class BookRepositoryTests
{
    private readonly DatabaseFixture _fixture;
    public BookRepositoryTests(DatabaseFixture fixture) => _fixture = fixture;
}
A collection fixture is created once for every class in the collection — the right tool for something expensive like a database container. Note that classes in one collection do not run in parallel with each other.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Tests interfere with each other
Something is static or shared. Test classes are isolated per test; static state is not.
Async tests pass without asserting
The test returns void instead of Task, so the runner does not await it. Return Task from async tests.

Best practices

  • Use the constructor for setup and IDisposable for teardown; xUnit has no [SetUp] by design.
  • Prefer [Theory] with InlineData over near-duplicate test methods.
  • Use collection fixtures for expensive shared resources, and be aware they disable parallelism within the collection.
  • Pair with FluentAssertions for failure messages that explain what differed.

Background

Why it exists, and what it was reacting to.

Written by the original NUnit authors, xUnit removed features they had come to regard as mistakes — notably setup and teardown attributes, replaced by ordinary constructors and IDisposable.