Database Reset for Testing
Weasel provides an FK-aware database cleaner for EF Core that makes integration testing straightforward. Inspired by Respawn and Marten's ResetAllData(), the cleaner discovers tables from your DbContext metadata, resolves foreign key ordering, and generates provider-specific SQL to safely truncate all data.
Setup
Register the cleaner and optional seed data in your DI container:
var builder = Host.CreateDefaultBuilder();
builder.ConfigureServices(services =>
{
services.AddDbContext<ShopDbContext>(options =>
options.UseNpgsql("Host=localhost;Database=mydb"));
// Register the Weasel migrator for your database provider
services.AddSingleton<Migrator, Weasel.Postgresql.PostgresqlMigrator>();
// Register the database cleaner
services.AddDatabaseCleaner<ShopDbContext>();
// Register seed data (optional, runs after ResetAllDataAsync)
services.AddInitialData<ShopDbContext, TestOrderSeedData>();
});Deleting All Data
Use DeleteAllDataAsync() to truncate all tables in FK-safe order (children before parents):
var cleaner = host.Services.GetRequiredService<IDatabaseCleaner<ShopDbContext>>();
// Delete all data from tables managed by the DbContext
// Tables are truncated in FK-safe order (children first)
await cleaner.DeleteAllDataAsync();Behind the scenes, the cleaner generates provider-specific SQL:
- PostgreSQL:
TRUNCATE TABLE t1, t2, t3 RESTART IDENTITY CASCADE; - SQL Server:
DELETE FROMeach table in dependency order, thenDBCC CHECKIDENTto reseed - SQLite:
DELETE FROMeach table in dependency order, then clearssqlite_sequence
Reset with Seed Data
Use ResetAllDataAsync() to delete all data and then run all registered IInitialData<TContext> seeders:
var cleaner = host.Services.GetRequiredService<IDatabaseCleaner<ShopDbContext>>();
// Delete all data, then run all registered IInitialData<T> seeders
await cleaner.ResetAllDataAsync();IInitialData
Implement IInitialData<TContext> to define seed data applied after a reset. Multiple seeders execute in registration order.
public class TestOrderSeedData : IInitialData<ShopDbContext>
{
public async Task Populate(ShopDbContext context, CancellationToken cancellation)
{
context.Customers.Add(new ShopCustomer { Name = "Test Customer" });
context.Orders.Add(new ShopOrder { CustomerId = 1, Status = "Pending" });
await context.SaveChangesAsync(cancellation);
}
}Multi-Tenancy
For multi-tenant scenarios where each tenant has its own database, pass an explicit DbConnection to target a specific tenant:
var cleaner = host.Services.GetRequiredService<IDatabaseCleaner<ShopDbContext>>();
// For multi-tenant scenarios, pass an explicit connection
// to target a specific tenant database
await using var tenantConnection = new Npgsql.NpgsqlConnection(
"Host=localhost;Database=tenant_abc");
await cleaner.DeleteAllDataAsync(tenantConnection);
// or with seeding:
await cleaner.ResetAllDataAsync(tenantConnection);Performance
The cleaner memoizes the table dependency graph and generated SQL on first use. The EF Core model is immutable, so the SQL never changes. This means subsequent calls in a test suite reuse the cached SQL with zero overhead — critical when running hundreds of tests.
Supported Providers
| Provider | Strategy | Identity Reset |
|---|---|---|
| PostgreSQL | TRUNCATE ... CASCADE RESTART IDENTITY | Automatic |
| SQL Server | DELETE FROM in FK order | DBCC CHECKIDENT |
| SQLite | DELETE FROM in FK order | Clears sqlite_sequence |
| MySQL | TRUNCATE TABLE with FOREIGN_KEY_CHECKS=0 | Automatic |
| Oracle | DELETE FROM in FK order | N/A (uses sequences) |
