Testing with a database
Run model tests against a real database: SQLite for fast local tests, or a test Postgres for the real thing. Migrations run once, and every test starts with an empty table.
What you need
- A Dox app with Dart migrations in
db/migration(see Migration). testas a dev dependency:dart pub add dev:test.
1. Write the migration
If you do not have one yet:
import 'package:dox_migration/dox_migration.dart';
class CreateUsersTable extends Migration {
@override
Future<void> up(Schema schema) async {
await schema.create('users', (Table table) {
table.id();
table.string('name');
table.string('email').unique();
table.string('password');
table.timestamps();
table.softDeletes();
});
}
@override
Future<void> down(Schema schema) async {
await schema.dropIfExists('users');
}
}
The command also writes db/migration/migrations.dart, the list of all migrations. The test imports that list.
A Dart migration works on Postgres, MySQL and SQLite, so the same test runs on all of them.
2. Write the test (SQLite)
SQLite needs no server. The test makes its own config, so your .env does not matter.
import 'dart:io';
import 'package:dox_core/dox_core.dart';
import 'package:dox_migration/dox_migration.dart';
import 'package:dox_orm/dox_orm.dart';
import 'package:my_app/app/models/user/user.model.dart';
import 'package:test/test.dart';
import '../db/migration/migrations.dart';
/// One SQLite file for this test file. No server, no Postgres.
const String dbFile = 'storage/test.sqlite';
DatabaseConfig testDatabase = DatabaseConfig(
connection: 'sqlite',
connections: <String, ConnectionConfig>{
'sqlite': ConnectionConfig(
driver: Driver.sqlite,
database: dbFile,
debug: false,
),
},
);
Future<void> deleteDbFile() async {
for (String suffix in <String>['', '-wal', '-shm']) {
File file = File('$dbFile$suffix');
if (await file.exists()) await file.delete();
}
}
void main() {
setUpAll(() async {
await deleteDbFile();
await Migrator(
migrations: migrations,
connection: await testDatabase
.getConnectionConfig()
.getDatabaseConnection(),
).migrate();
SqlQueryBuilder.initializeWithDatabaseConfig(testDatabase);
});
// every test starts with an empty table
setUp(() async {
await User().truncate();
});
tearDownAll(() async {
await deleteDbFile();
});
test('saves and finds a user', () async {
User user = User()
..name = 'Ann'
..email = 'ann@example.com'
..password = Hash.make('secret123');
await user.save();
User? found = await User().where('email', 'ann@example.com').getFirst();
expect(found?.id, user.id);
expect(found?.name, 'Ann');
expect(Hash.verify('secret123', found!.password!), isTrue);
});
test('the table is empty again', () async {
expect(await User().count(), 0);
});
}
What happens:
setUpAlldeletes an old database file, runs the migrations, and starts the ORM on the same file.setUpempties the table before each test.truncate()also resets the id counter.tearDownAlldeletes the file.
A file, not :memory:
The migrator and the ORM each open their own connection.
Two connections to :memory: are two different databases. A file is shared.
Put storage/test.sqlite in .gitignore.
3. Run it
Check it
00:00 +0: (setUpAll)
Migrating: 2026_10_22_000000_create_users_table
Migrated: 2026_10_22_000000_create_users_table
00:00 +1: saves and finds a user
00:00 +2: the table is empty again
00:00 +2: All tests passed!
The same test on Postgres
Use a separate database for tests. Never the one you develop on.
Start one with Docker:
docker run -d --name my_app_test -p 5433:5432 \
-e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=my_app_test postgres:18
Change the config at the top of the test:
DatabaseConfig testDatabase = DatabaseConfig(
connection: 'postgres',
connections: <String, ConnectionConfig>{
'postgres': ConnectionConfig(
driver: Driver.postgres,
host: 'localhost',
port: 5433,
user: 'postgres',
password: 'postgres',
database: 'my_app_test',
debug: false,
),
},
);
Delete deleteDbFile() and its two calls. The rest is the same.
Or read the values from the env, and run DB_PORT=5433 DB_NAME=my_app_test dart test.
Many test files
Each test file runs in its own process. Two files that use the same table at the same time break each other.
Either give each file its own SQLite file (storage/test_users.sqlite, storage/test_posts.sqlite), or run them one by one:
Test a route with the database
Start the server in setUpAll and send real requests. The server runs the services, so the ORM is set up by ORMService.
Set the database with env variables, so lib/config/database.dart picks the test database:
See Testing: test HTTP routes for the server setup.