Skip to content

Getting started

Add the base class for your test framework and one package per engine you test against.

Install
dotnet add package DbTest.MSTest # base class for MSTest (brings DbTest.Core)
dotnet add package DbTest.MsSql # SQL Server engine
dotnet add package DbTest.PostgreSql # PostgreSQL engine
dotnet add package DbTest.MySql # MySQL engine
dotnet add package DbTest.MsSql.Dacpac # deploy a .dacpac (SQL Server database projects)

DbTest.Core alone gives the harness without any test framework. DbTest.MSTest brings it in.

For the command-line tool, see the CLI.

  • Docker must be running. The harness starts real containers through Testcontainers.
  • .NET 8 or newer. The packages ship a net8.0 and a net10.0 asset. .NET 8 and 9 resolve the net8.0 one, .NET 10 and later the net10.0 one.

Put your SQL files in one directory per engine. The schemas below are examples: any DDL your tests need will do.

Schema/mssql/001_tables.sql
CREATE TABLE dbo.customers (
id INT IDENTITY(1, 1) PRIMARY KEY,
email NVARCHAR(256) NOT NULL,
display_name NVARCHAR(256) NOT NULL
);

Copy the files next to the test binaries, so the relative SchemaRoot resolves:

<None Include="Schema\**\*" CopyToOutputDirectory="PreserveNewest" />

See the schema for ordering, splitting and exclusion rules.

Derive from DbTestBase in an abstract class. Harness is the shared harness for the engine under test.

public abstract class CustomerTests : DbTestBase
{
[TestMethod]
public async Task Inserted_Customer_Should_Be_Counted()
{
await Harness.CleanAsync();
await Harness.ExecuteAsync(
"INSERT INTO customers (email, display_name) VALUES (@email, @name)",
[new DbParameterValue("@email", "ada@example.test"), new DbParameterValue("@name", "Ada")]);
var count = await Harness.QueryScalarAsync<int>(
"SELECT COUNT(*) FROM customers WHERE email = @email",
[new DbParameterValue("@email", "ada@example.test")]);
Assert.AreEqual(1, count);
}
}

The namespaces are DbTest.Core.Engine and DbTest.MSTest, plus one per engine: DbTest.MsSql, DbTest.PostgreSql or DbTest.MySql.

Each class names the engine and where its schema lives. Add only the engines you need.

[TestClass]
public sealed class MsSqlCustomerTests : CustomerTests
{
protected override DbEngine CreateEngine() => new MsSqlEngine();
protected override string SchemaRoot => "Schema/mssql";
}

CreateEngine() is called before every test, but only the first call per run starts a container.

Tests share a container, so they run one at a time, and the containers should be removed as soon as the run ends.

// Once per test project: tests share a container, so they run one at a time, and the
// containers are removed as soon as the run ends.
[assembly: DoNotParallelize]
[TestClass]
public static class TestRun
{
[AssemblyCleanup]
public static Task Cleanup() => DbTestRun.DisposeAllAsync();
}

DbTestRun is in the DbTest.MSTest namespace.

Without DisposeAllAsync(), Testcontainers’ resource reaper removes the containers when the test host exits.

Terminal window
dotnet test

The first test of each engine starts its container and applies the schema. Later tests in the run reuse it.

If Docker is not running, the first test fails with the real error and every later test fails fast with the same one, instead of each waiting for its own container to time out.