- Swift 100%
| Sources | ||
| Tests/DatabaseTestingTests | ||
| .gitignore | ||
| .swiftformat | ||
| AGENTS.md | ||
| Package.resolved | ||
| Package.swift | ||
| README.md | ||
DatabaseTesting
Ephemeral PostgreSQL databases for Swift Testing. Spins up Docker containers on demand, pools them for concurrent test execution, and tears them down when the suite finishes.
Requirements
- Swift 6.3+ / swift-tools-version 6.3
- macOS 26+
- Docker (accessible at
/usr/local/bin/docker)
Installation
Add the package to your Package.swift:
dependencies: [
.package(url: "https://github.com/noahkamara/swift-database-testing", from: "0.1.0"),
]
Then add the module you need to your test target:
.testTarget(
name: "MyTests",
dependencies: [
.product(name: "DatabaseTesting", package: "swift-database-testing"),
]
)
Usage
Apply the .database() trait to a suite or test. The trait manages the pool lifecycle automatically -- containers are created as needed, reset to a clean baseline every time they are retained, and destroyed when the outermost suite finishes.
import Testing
import DatabaseTesting
@Suite(.database())
struct MyDatabaseTests {
@Test func insertAndQuery() async throws {
let db = try TestDatabase.current()
// db.host, db.port, db.username, db.password, db.databaseName
// are all available to connect your client of choice.
}
}
Preparing the database
Pass a prepare closure to run setup (migrations, seed data, etc.) against each database after the pool restores its clean baseline for the current test scope:
@Suite(.database(prepare: { db in
// Run migrations, create tables, insert seed data.
}))
struct MigrationTests {
@Test func readsSeedData() async throws {
let db = try TestDatabase.current()
// ...
}
}
Modules
| Module | Purpose |
|---|---|
DatabaseTestingCore |
Docker container lifecycle, TestDatabase, DatabasePool, retry helpers. No dependency on Swift Testing. |
DatabaseTesting |
Swift Testing integration: DatabaseTrait, TaskLocal context, .database() trait API. |
Import DatabaseTestingCore directly if you want the pool and container management without the Swift Testing trait (e.g., for XCTest or custom harnesses).
Configuration
The pool reads its configuration from environment variables at first use:
| Variable | Default | Description |
|---|---|---|
TEST_DB_CAPACITY |
8 |
Maximum number of concurrent database containers. |
TEST_DB_IMAGE |
postgres:17-alpine |
Docker image to use for PostgreSQL. |
How it works
- The first time a test needs a database,
PoolRegistryinitializes. It looks for existing containers labeledtestdb.managed=true, rebuilds their clean baselines, and reclaims them so a crashed previous run doesn't leak containers. - When a test retains a database and none are available, a new container is launched up to the configured capacity, initialized with a canonical clean baseline, and restored before it is handed out. If the pool is full, the test waits (with a 10-second timeout).
- User-provided
.database(prepare:)closures are stacked by suite/test scope and applied in order after the pool restores that clean baseline. - Containers use tmpfs-backed
PGDATAfor faster writes and ephemeral storage. - Each container gets a stable indexed name (
testdb_<index>), user, database name, and companion baseline database. The host port is dynamically assigned (-p 0:5432). - When the outermost
DatabaseTrait-scoped suite finishes, all containers are destroyed in parallel.
License
See LICENSE for details.