Skip to main content
Version: Next

Configuration

The withServerpod helper takes optional named arguments that control how the test server starts and how tests are isolated from each other.

Some of the values below, such as ServerpodRunMode and Constant, come from package:serverpod/serverpod.dart rather than the generated test tools, so import it when you use them. The serverDirectory option takes a Directory from dart:io.

withServerpod(
'Given Products endpoint',
(sessionBuilder, endpoints) {
/* test code */
},
rollbackDatabase: RollbackDatabase.afterAll,
);

Options

OptionWhat it doesDefault
applyMigrationsWhether pending migrations are applied when the test server starts.true
configOverrideA function that changes values in the loaded config before the server starts.null
enableSessionLoggingWhether session logging is on.false
experimentalFeaturesExperimental features to enable, such as diagnostic event handlers.null
rollbackDatabaseWhen database changes are rolled back. See below.RollbackDatabase.afterEach
runModeThe run mode the server starts in.test
runtimeParametersBuilderRuntime parameters to apply before startup. See runtime parameters.null
serverDirectoryThe directory that config/<runMode>.yaml, config/passwords.yaml, and migrations/ are resolved against.The directory the test process runs in
serverpodLoggingModeThe logging mode used when creating Serverpod.ServerpodLoggingMode.normal
serverpodStartTimeoutHow long to wait for the server to start.30 seconds
testGroupTagsOverrideThe tags applied to the generated test group. See below.['integration']
testServerOutputModeHow much server output reaches your terminal. See below.TestServerOutputMode.normal
info

In a project without a database, applyMigrations, rollbackDatabase, and runtimeParametersBuilder are not generated at all, so passing one is a compile error. Rollback is off in that case and there is nothing to migrate.

Set serverDirectory when tests run from somewhere other than the server package, such as the workspace root. Without it the server resolves those paths against the current directory, misses your config, and falls over later on whatever it needed from it.

Run mode

Tests run in the test run mode, so the server reads config/test.yaml. Point them at a different mode when you need its configuration instead:

withServerpod(
'Given Products endpoint',
(sessionBuilder, endpoints) {
/* test code */
},
runMode: ServerpodRunMode.development,
);

Database rollback

By default each test runs inside a transaction that is rolled back when that test ends, so tests cannot see each other's data. Three values control this:

ValueRolls back
RollbackDatabase.afterEachAfter every test. The default.
RollbackDatabase.afterAllOnce the whole withServerpod group finishes.
RollbackDatabase.disabledNever. You clean up yourself.

Three situations call for changing it.

Tests that build on each other. When one test depends on what the previous one left behind, afterAll keeps state across the group and still rolls everything back at the end. Tests that share state are usually worth avoiding, but this helps when the setup is expensive.

Concurrent database work in an endpoint. The rollback wraps each test in one transaction, and concurrent calls cannot be nested inside it. An endpoint that runs database calls or transactions in parallel throws InvalidConfigurationException unless you pass RollbackDatabase.disabled:

Future<void> concurrentTransactionCalls(Session session) async {
await Future.wait([
session.db.transaction((tx) => /*...*/),
// Throws InvalidConfigurationException unless rollbackDatabase
// is RollbackDatabase.disabled.
session.db.transaction((tx) => /*...*/),
]);
}

With rollback disabled you clean up yourself, or later tests inherit the data:

withServerpod(
'Given ProductsEndpoint when calling concurrentTransactionCalls',
(sessionBuilder, endpoints) {
tearDownAll(() async {
var session = sessionBuilder.build();
await Product.db.deleteWhere(session, where: (_) => Constant.bool(true));
});

test('then it commits all transactions', () async {
var result =
await endpoints.products.concurrentTransactionCalls(sessionBuilder);
// ...
});
},
rollbackDatabase: RollbackDatabase.disabled,
);

Tests that share a database this way also need to run one at a time, since the test runner runs files in parallel by default:

dart test -t integration --concurrency=1

This applies only when rollback is disabled. With rollback on, each withServerpod group has its own transaction and is already isolated from the others.

Caught database exceptions. This is the one case where the test tools behave differently from production:

var transactionFuture = session.db.transaction((tx) async {
var data = UniqueData(number: 1, email: 'test@test.com');
try {
await UniqueData.db.insertRow(session, data, transaction: tx);
await UniqueData.db.insertRow(session, data, transaction: tx);
} on DatabaseException catch (_) {
// Ignored.
}
});

// Throws in production, but not under the test tools.
await transactionFuture;

In production a transaction fails if any database exception happened inside it, even one your code caught. The test tools emulate nested transactions and do not, so code that swallows database exceptions passes a test it would fail in production. Set RollbackDatabase.disabled to get production behavior.

Server output

The testServerOutputMode option controls what the test server prints, which is the setting to reach for when you cannot see why a server failed:

ValueShows
TestServerOutputMode.normalErrors only. The default.
TestServerOutputMode.verboseEverything the server prints.
TestServerOutputMode.silentNothing.

Test tags

Test groups created by withServerpod are tagged integration, which is what lets you run integration and unit tests separately. Change the tag with testGroupTagsOverride. See running tests separately.