Getting started
Install
Section titled “Install”Add the base class for your test framework and one package per engine you test against.
dotnet add package DbTest.MSTest # base class for MSTest (brings DbTest.Core)dotnet add package DbTest.MsSql # SQL Server enginedotnet add package DbTest.PostgreSql # PostgreSQL enginedotnet add package DbTest.MySql # MySQL enginedotnet 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.
Requirements
Section titled “Requirements”- Docker must be running. The harness starts real containers through Testcontainers.
- .NET 8 or newer. The packages ship a
net8.0and anet10.0asset. .NET 8 and 9 resolve thenet8.0one, .NET 10 and later thenet10.0one.
1. Add a schema
Section titled “1. Add a schema”Put your SQL files in one directory per engine. The schemas below are examples: any DDL your tests need will do.
CREATE TABLE dbo.customers ( id INT IDENTITY(1, 1) PRIMARY KEY, email NVARCHAR(256) NOT NULL, display_name NVARCHAR(256) NOT NULL);CREATE TABLE customers ( id SERIAL PRIMARY KEY, email TEXT NOT NULL, display_name TEXT NOT NULL);CREATE TABLE customers ( id INT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(256) NOT NULL, display_name VARCHAR(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.
2. Write the test once
Section titled “2. Write the test once”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.
3. Add one class per engine
Section titled “3. Add one class per engine”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";}[TestClass]public sealed class PostgresCustomerTests : CustomerTests{ protected override DbEngine CreateEngine() => new PostgreSqlEngine(); protected override string SchemaRoot => "Schema/postgres";}[TestClass]public sealed class MySqlCustomerTests : CustomerTests{ protected override DbEngine CreateEngine() => new MySqlEngine(); protected override string SchemaRoot => "Schema/mysql";}CreateEngine() is called before every test, but only the first call per run starts a container.
4. Configure the test project once
Section titled “4. Configure the test project once”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.
5. Run it
Section titled “5. Run it”dotnet testThe 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.
- Engines: images, logins and container options.
- Shape guard and cleanup: what runs around every test.
- Troubleshooting.