patterncsharpdotnetTip
xUnit: test isolation with IClassFixture and shared expensive setup
Viewed 0 times
xUnit IClassFixtureshared test fixtureIAsyncLifetimeWebApplicationFactory fixturetest isolation xunit
Problem
Creating expensive resources (database connections, WebApplicationFactory) in each test constructor is slow. Using static state across tests causes test pollution and flakiness.
Solution
Use IClassFixture<T> to share a single instance across all tests in a class:
// Fixture sets up expensive resource once
public class DatabaseFixture : IAsyncLifetime
{
public AppDbContext Db { get; private set; } = null!;
public async Task InitializeAsync()
{
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
Db = new AppDbContext(options);
await Db.Database.EnsureCreatedAsync();
}
public async Task DisposeAsync() => await Db.DisposeAsync();
}
// Tests share the fixture
public class OrderTests : IClassFixture<DatabaseFixture>
{
private readonly AppDbContext _db;
public OrderTests(DatabaseFixture fixture)
=> _db = fixture.Db;
[Fact]
public async Task CreateOrder_Saves_ToDatabase()
{
var order = new Order { /* ... */ };
_db.Orders.Add(order);
await _db.SaveChangesAsync();
Assert.Equal(1, await _db.Orders.CountAsync());
}
}Why
xUnit creates a new test class instance per test method to ensure isolation. IClassFixture makes the fixture creation happen once per class and injects it. ICollectionFixture shares across multiple test classes.
Gotchas
- Shared fixture means shared state — tests that mutate data can affect each other; use unique identifiers or transactions
- Async setup requires IAsyncLifetime — InitializeAsync and DisposeAsync, not constructors
- WebApplicationFactory should be shared via IClassFixture to avoid port conflicts and slow startup per test
Revisions (0)
No revisions yet.