xUnit.net is the modern, open-source unit testing tool for .NET (C#, F#, VB). It is chosen by Microsoft/dotnet for their own repositories.
When to Use
- Modern .NET Core Unit Testing: The modern, opinionated, community-preferred testing framework for C# and ASP.NET Core applications.
- Isolated Clean Test Execution: Enforcing a new class instance for every single test method to guarantee zero state contamination.
- Theory-Driven Parameterized Tests: Running tests across inline data (
[InlineData]), member data, and class data sources. - Constructor-Based Test Setup: Replacing
[SetUp]attributes with standard C# object-oriented constructors andIDisposable.
Quick Start
using Xunit;
public class CalculatorTests { [Fact] public void PassingTest() { Assert.Equal(4, Add(2, 2)); }
[Theory]
[InlineData(3)]
[InlineData(5)]
[InlineData(6)]
public void MyTheory(int value)
{
Assert.True(IsOdd(value));
}
}
## Core Concepts
### Instance-Per-Test Lifecycle Architecture
Unlike NUnit and MSTest, xUnit instantiates a completely new instance of the test class for every `[Fact]`, preventing shared instance field state:
```text
[ Test Class ] ──new()──→ Runs Fact 1 ──Dispose()
[ Test Class ] ──new()──→ Runs Fact 2 ──Dispose()
Facts vs Theories ([Fact] vs [Theory])
[Fact]: Test that is always true and tests invariant conditions.[Theory]: Parameterized test that executes across datasets:
using Xunit;
public class OrderValidatorTests
{
[Fact]
public void EmptyOrder_IsInvalid()
{
var order = new Order();
Assert.False(order.IsValid());
}
[Theory]
[InlineData(100, "DISCOUNT10", 90)]
[InlineData(50, "SAVE5", 45)]
[InlineData(20, "", 20)]
public void ApplyDiscount_CalculatesCorrectTotal(decimal price, string code, decimal expected)
{
var total = OrderService.CalculateTotal(price, code);
Assert.Equal(expected, total);
}
}
Shared Context via Class Fixtures (IClassFixture<T>)
Shares expensive setup (database, web test servers) across tests without static state:
public class DatabaseFixture : IDisposable
{
public SqlConnection Connection { get; }
public DatabaseFixture() { Connection = new SqlConnection("Server=localhost;..."); Connection.Open(); }
public void Dispose() { Connection.Dispose(); }
}
public class CustomerRepositoryTests : IClassFixture<DatabaseFixture>
{
private readonly DatabaseFixture _fixture;
public CustomerRepositoryTests(DatabaseFixture fixture) => _fixture = fixture;
[Fact]
public void QueriesCustomerRecord() { /* Uses _fixture.Connection */ }
}
Common Patterns
Theory with InlineData and ClassData
Problem: Writing duplicate test methods for multiple inputs in C# applications.
Solution:
Use [Theory] with [InlineData]:
using Xunit;
public class MathTests
{
[Theory]
[InlineData(2, 3, 5)]
[InlineData(-1, 1, 0)]
[InlineData(0, 0, 0)]
public void Add_ValidInputs_ReturnsExpectedSum(int a, int b, int expected)
{
var calculator = new Calculator();
var result = calculator.Add(a, b);
Assert.Equal(expected, result);
}
}
Best Practices
Do:
- Use Constructors for Setup and
Dispose()for Teardown: ImplementIDisposableorIAsyncLifetimerather than looking for[SetUp]. - Use
IClassFixture<T>for Shared State: Share heavy dependencies (like Testcontainers or WebApplicationFactory) cleanly. - Inject
ITestOutputHelperfor Logging: Write test logs viaITestOutputHelperrather thanConsole.WriteLine(). - Run Tests in Parallel: Leverage xUnit's default parallelization across test collections.
Don't:
- Use static variables in test classes: xUnit runs test classes in parallel; static state introduces race conditions.
- Write
Assert.True(x == y): UseAssert.Equal(expected, actual)for informative failure diff messages. - Create asynchronous void tests: Always return
async Taskfrom test methods;async voidexceptions crash the test runner.
Troubleshooting
| Error | Cause | Solution |
|---|---|---|
No test matches the given testcase filter |
Filter string doesn't match namespace or class name. | Run dotnet test --filter FullyQualifiedName~MathTests. |
Console.WriteLine produces no output |
xUnit isolates console output to prevent race conditions. | Inject ITestOutputHelper in constructor and call _output.WriteLine(). |
Assert.Equal() Failure: Expected ... Actual ... |
Output mismatch in assertion. | Review diff and check floating-point precision with tolerance arguments. |