Test without Database installing – I use Testcontainers
For SQL Server, PostgreSQL, and MongoDB, I use Testcontainers to run the
database engines in disposable Docker containers during the integration tests.
This lets me test the real EF Core providers instead of replacing them with
mocks or an in-memory database.
The test project references the provider-specific Testcontainers packages:
<PackageReference Include="Testcontainers.MsSql" Version="4.15.0" /> <PackageReference Include="Testcontainers.PostgreSql" Version="4.15.0" /> <PackageReference Include="Testcontainers.MongoDb" Version="4.15.0" />
Each database test creates a container builder with the image, credentials,
and other provider settings. For example, my SQL Server test uses the
SQL Server 2022 image:
new MsSqlBuilder("mcr.microsoft.com/mssql/server:2022-latest")
.WithPassword("Passw0rd!")
.Build();
My PostgreSQL test uses `postgres:17-alpine`, and my MongoDB tests use
`mongo:8.3`. MongoDB has two configurations: one starts with credentials and
the other starts without authentication. This allows me to run the same
database checks against both container configurations.
I keep the common container lifecycle in `DatabaseContainerTest`. The fixture
implements xUnit’s `IAsyncLifetime`, so xUnit calls `InitializeAsync` before
the tests and `DisposeAsync` afterward:
public async ValueTask InitializeAsync()
{
await _dockerContainer.StartAsync()
.ConfigureAwait(false);
}
protected virtual async Task DisposeAsyncCore()
{
await _dockerContainer.DisposeAsync();
}
When the container starts, Testcontainers maps the database service to an
available host port and exposes the correct connection string through
`GetConnectionString()`. I pass that connection string directly to the
selected `IEFCore_DatabasePlugin`:
var connectionString = _dockerContainer.GetConnectionString();
var optionsBuilder =
plugin.GenerateDbContextOptionsBuilder<EmpContext>(
connectionString,
"EmpContextDatabase");
The provider-specific test classes only supply two things to the shared
fixture: the Docker container and the matching plugin. The shared test then
starts the container, creates the EF Core context, calls `EnsureCreated`, and
disposes the container when the test lifecycle finishes.
I also use the native client for each database to verify the service
independently of EF Core. SQL Server opens a `SqlConnection` and counts rows
from `sys.databases`. PostgreSQL opens an `NpgsqlConnection` and queries
`pg_database`. MongoDB uses `MongoClient.ListDatabases`. These catalog checks
confirm that the container is reachable and give me a provider-specific way
to verify that `EnsureCreated` added a database.
The result is an integration test that is isolated and repeatable: I start
the database when the test begins, obtain its connection details dynamically,
run the same EF Core setup against a real provider, and remove the container
afterward. SQLite does not need Testcontainers because the SQLite test can use
a unique temporary database file directly.