DatabaseLifecycleFixture
Assembly: FEFF.TestFixtures.AspNetCore.EF.dll
Namespace: FEFF.TestFixtures.AspNetCore.EF
Source: DatabaseLifecycleFixture.cs
Overview
The DatabaseLifecycleFixture is an extension to AppServicesFixture.
It manages Entity Framework Core database creation and deletion for the lifetime of a test scope.
This fixture ensures that the database exists and is properly migrated before tests, and automatically cleans up the database after tests complete.
Key Features
- Automatic Cleanup: Deletes the database automatically when the fixture is disposed
- Manual Database Creation: Ensures the database exists and the schema is applied via
EnsureCreatedAsync() - DbContext Access: Exposes the
DbContextinstance via theLazyDbContextproperty - Test Isolation: When combined with
TmpDatabaseNameFixture, each test gets a unique temporary database
Basic Usage
First, ensure your DbContext is registered in the application's DI container:
// ASP.NET Core test application entry point (Program.cs)
public class Program
{
public const string ConnectionStringName = "DefaultConnection";
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Register your DbContext
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString(ConnectionStringName))
);
var app = builder.Build();
// Receives weather forecast and saves it to the database
app.MapPost("/weatherforecast", async (ApplicationDbContext dbCtx, WeatherForecast forecast) =>
{
dbCtx.WeatherForecasts.Add(forecast);
await dbCtx.SaveChangesAsync();
});
app.Run();
}
}
Then use the fixture in your tests:
using FEFF.TestFixtures.AspNetCore;
using FEFF.TestFixtures.AspNetCore.EF;
using Microsoft.EntityFrameworkCore;
public class DatabaseTests
{
// Request the fixtures
protected DatabaseLifecycleFixture<Program, ApplicationDbContext> DbFx { get; } =
TestContext.Current.GetFeffFixture<DatabaseLifecycleFixture<Program, ApplicationDbContext>>();
protected IAppClientFixture ClientFx { get; } =
TestContext.Current.GetFeffFixture<AppClientFixture<Program>>();
// Properties for convenient access
protected ApplicationDbContext AppDbCtx => DbFx.LazyDbContext;
protected HttpClient Client => ClientFx.LazyValue;
[Fact]
public async Task Create_WeatherForecast__should_persist_to_database()
{
// Ensure the database is created and migrations are applied
await DbFx.EnsureCreatedAsync(TestContext.Current.CancellationToken);
// Perform database operations
// Send a POST request to the /weatherforecast endpoint to create a new weather forecast
await PostAsync(Client, """
{
"date": "2000-01-01",
"temperatureC": 20,
"summary": "normal"
}
""");
// Verify the data was persisted
var forecasts = await AppDbCtx.WeatherForecasts.ToListAsync(TestContext.Current.CancellationToken);
// Assert the weather forecasts table contains exactly one record with expected properties
JToken.FromObject(forecasts)
.Should().BeEquivalentTo("""
[
{
"Date": "2000-01-01",
"TemperatureC": 20,
"Summary": "normal",
}
]
""");
}
}
// The database is automatically deleted after the test completes
}
Type Arguments
| Type Argument | Constraint | Description |
|---|---|---|
TEntryPoint |
class |
The application entry point type. Typically the Startup or Program class. |
TContext |
DbContext |
The DbContext type to manage for database lifecycle operations. |
Key Members
| Property | Type | Description |
|---|---|---|
LazyDbContext |
TContext |
Gets the DbContext instance resolved from the service provider. Starts the application under test if it is not already running. |
| Method | Returns | Description |
|---|---|---|
EnsureCreatedAsync(CancellationToken) |
Task |
Ensures that the database for the context exists and is created. Applies all pending migrations. Starts the application under test if it is not already running. |
Note
The database is deleted in DisposeAsync() only if the application was started during the test.
Advanced Usage: Combining with TmpDatabaseNameFixture
For complete test isolation, combine DatabaseLifecycleFixture with TmpDatabaseNameFixture. This creates a unique temporary database for each test scope, ensuring tests don't interfere with each other.
Example: Full Integration Setup
using FEFF.TestFixtures.AspNetCore;
using FEFF.TestFixtures.AspNetCore.EF;
// Define options for TmpDatabaseNameFixture
[Fixture]
public class TestOptionsFixture : ITmpDatabaseNameFixtureOptions
{
public IReadOnlyCollection<string> ConnectionStringNames =>
[Program.ConnectionStringName];
}
// All fixtures are instantiated together
[Fixture]
public record FixtureSet(
DatabaseLifecycleFixture<Program, ApplicationDbContext> EnsureDbFx,
TmpDatabaseNameFixture<Program, TestOptionsFixture> TmpDbNameFx,
AppClientFixture<Program> ClientFx
);
public class IsolatedDatabaseTests
{
protected FixtureSet FixtureSet { get; } =
TestContext.Current.GetFeffFixture<FixtureSet>();
protected ApplicationDbContext AppDbCtx =>
FixtureSet.EnsureDbFx.LazyDbContext;
protected HttpClient Client =>
FixtureSet.ClientFx.LazyValue;
// Each test gets its own unique database
[Fact]
public async Task Create_WeatherForecast__should_persist_to_database()
{
// TmpDatabaseNameFixture automatically redirects the connection string
// to a unique temporary database (e.g., "TestDb_a1b2c3d4")
// This happens during AppManagerFixture setup, before the application is started and the database is created
// Ensure the database is created and migrations are applied
// This call starts the application
await EnsureDbFx.EnsureCreatedAsync(TestContext.Current.CancellationToken);
// ... the test remains the same
}
// The database is automatically deleted after the test completes
}
How It Works
- TmpDatabaseNameFixture intercepts the connection string during application configuration and redirects it to a uniquely named temporary database (e.g.,
TestDb_<guid>) - Test starts the application by calling
EnsureDbFx.EnsureCreatedAsync() - DatabaseLifecycleFixture creates the database and applies migrations to this unique database
- Tests run in complete isolation with no cross-test contamination
- The temporary database is automatically deleted after the test completes
Benefits
- Complete Isolation: Each test scope gets its own database with no shared state
- No Cleanup Required: Temporary databases are automatically deleted on test completion
- Parallel Execution: Tests can run in parallel without database conflicts or race conditions
- Clean Execution: Tests start with a fresh database each time
- Realistic Testing: Tests run against a real database, not in-memory mocks
See Also
| Link | Description |
|---|---|
| API: DatabaseLifecycleFixture | API reference |
| DatabaseLifecycleFixture Tests | Unit tests for DatabaseLifecycleFixture |
| TmpDatabaseNameFixture | Create unique temporary databases for test isolation |
| API Integration Example | Complete integration test examples |