SpacetimeDB has good test coverage, but it is rather haphazardly spread across several suites. Some of the reasons for this are historical, and some have to do with our using multiple repositories. This document is an attempt to describe the test suites which would be useful to someone working on a new client SDK or module bindings library in a new language, or attempting to move an existing client SDK or module bindings library from an external repository in-tree.
crates/testing/src/sdk.rs defines a test harness which was originally designed for testing client SDKs.
The basic flow of a test using this harness is:
- Build and freshly publish a module to construct a short-lived initially-empty database.
- Use that module to run client codegen via
spacetime generateinto a client project. - Compile that client project with the newly-generated bindings.
- Run the client project as a subprocess, passing the database name or
Identityin an environment variable. - The client process connects to the database, runs whatever tests it likes, writes to stdout and/or stderr as it goes, then uses its exit code to report whether the test was successful or not.
- If the subprocess's exit is non-zero, the test is treated as a failure, and the subprocess's stdout and stderr are reported.
This framework has since been used more generally for integration testing.
In particular, we maintain equivalent Rust, C#, TypeScript, and C++ modules in the modules/sdk-test* family,
and run the Rust SDK client project at sdks/rust/tests/test-client against them through sdks/rust/tests/test.rs.
We similarly maintain modules/sdk-test-connect-disconnect* modules
which run against sdks/rust/tests/connect_disconnect_client.
There are also related SDK-harness-driven suites for event tables, procedures, and views,
using modules such as modules/sdk-test-event-table, modules/sdk-test-procedure*, and modules/sdk-test-view*.
The Unreal SDK also uses the same underlying harness through sdks/unreal/tests/sdk_unreal_harness.rs.
The harness is designed to support running multiple tests in parallel with the same client project,
running client codegen exactly once per test suite run.
This unfortunately still conflicts with our use of the suite to test that modules in different languages behave the same,
as each test suite invocation will only run spacetime generate against one module language at a time,
never all of them in the same run.
If you are developing a new module bindings library, and wish to add it to the SDK test suite so that the existing client test projects will run against it:
- Create
modules/sdk-test-XXandmodules/sdk-test-connect-disconnect-XX, whereXXis some mnemonic for your language. Populate these with module code which defines all of the same tables and reducers asmodules/sdk-testandmodules/sdk-test-connect-disconnectrespectively. Take care to use the same names, including casing, for tables, columns, indexes, reducers and other database objects. - Modify
sdks/rust/tests/test.rsto add an additional call todeclare_tests_with_suffix!at the bottom, likedeclare_tests_with_suffix!(xxlang, "-XX"), if that driver is the right place for the new language. Some capabilities now live in separate suites in that file, such as procedures and views, so you may need to wire those up separately as well. - Run the tests with
cargo test -p spacetimedb-sdk --test test.
If you are developing a new client SDK, and wish to use the SDK test harness and existing modules
so that it will run against modules/sdk-test and modules/sdk-test-connect-disconnect:
- Find somewhere sensible to define test projects
test-clientandconnect_disconnect_clientfor your client SDK language. If your client SDK is in-tree, put these within its directory, following the existing layout undersdks/rust/tests/orsdks/unreal/tests/. - Use
spacetime generatemanually, or via the harness, to generate those projects'module_bindings. - Populate those projects with client code
matching
sdks/rust/tests/test-clientandsdks/rust/tests/connect_disconnect_clientrespectively.- Connect to SpacetimeDB running at
http://localhost:3000. - Connect to the database whose name is in the environment variable
SPACETIME_SDK_TEST_DB_NAME. - For
test-client, take a test name as a command-line argument inargv[1], and dispatch to the appropriate test to run. - For
connect_disconnect_client, there is only one test. - The Rust code jumps through some hoops to do assertions about asynchronous events with timeouts,
using an abstraction called the
TestCounterdefined insdks/rust/tests/test-counter. This is effectively a semaphore with a timeout. You may or may not need to replicate this behavior.
- Connect to SpacetimeDB running at
- Create integration tests in the SDK crate which construct
spacetimedb_testing::sdk::Testobjects, followingsdks/rust/tests/test.rsorsdks/unreal/tests/test.rsas a template. - Define
#[test]tests for each test case you have implemented, which constructspacetimedb_testing::sdk::Testobjects containing the various subcommand strings to run your client project, then call.run()on them.
If you want to add a new test case to the SDK test suite, to test some new or yet-untested functionality of either the module libraries or client SDKs:
- If necessary, add new tables and/or reducers to
modules/sdk-testand friends which exercise the behavior you want to test. - Add a new function,
exec_foo, to the appropriate client project, such assdks/rust/tests/test-client/src/lib.rs, which connects to the database, subscribes to tables and invokes reducers as appropriate, and performs assertions about the events it observes. - Add a branch to that client's dispatch logic,
such as the
matchinsdks/rust/tests/test-client/src/main.rs, which matches the test namefooand dispatches to call yourexec_foofunction. - Add a
#[test]test function to the relevant test driver, such assdks/rust/tests/test.rs, which doesmake_test("foo").run(), where"foo"is the test name you chose in step 3. - Repeat steps 2 through 4 for any other client projects which ought to cover the same behavior.
- Run the new test with the relevant
cargo testcommand for that SDK.
crates/schema/tests/ensure_same_schema.rs is a separate but important companion to the SDK tests.
It compares the extracted schemas of equivalent modules across languages,
and is often the first place where casing, indexes, primary keys, or other schema details drift apart.
As of writing, it covers the benchmarks, module-test, sdk-test, and sdk-test-connect-disconnect families.
If you add or update a cross-language module family, it is worth considering whether it should also be covered here.
crates/smoketests/ defines an integration and regression test suite using a Rust harness.
These are useful primarily for testing the SpacetimeDB CLI, but can also be used to exercise publish flows,
documentation, and other end-to-end behavior.
The smoketest harness is still primarily oriented around Rust modules, and it does not use the same client-project machinery as the SDK harness. It could be extended to do more in that direction, but that may not be worth the effort. As of writing, the smoketest suite includes dedicated coverage such as:
crates/smoketests/tests/smoketests/csharp_module.rs, which smoke-tests C# module compilation.crates/smoketests/tests/smoketests/quickstart.rs, which replays the quickstart guide for Rust and C#.crates/smoketests/DEVELOP.md, which documents how to run and write these tests.
One practical note is that the smoketests use prebuilt spacetimedb-cli and spacetimedb-standalone binaries,
so if you modify those crates or their dependencies, you should rebuild before running the suite.
The spacetimedb-testing crate has an integration test file, crates/testing/tests/standalone_integration_test.rs.
The tests in this file publish modules/module-test, modules/module-test-cs, modules/module-test-ts, and modules/module-test-cpp,
then invoke reducers or procedures in them and inspect their logs to verify that the behavior is expected.
These tests do not exercise the entire functionality of module-test,
but by virtue of publishing it do assert that it is syntactically valid and that it compiles.
To add a new module library to the Standalone integration test suite:
- Create
modules/module-test-XX, whereXXis some mnemonic for your language. - Populate this with module code which defines all of the same tables and reducers
as the existing
module-testfamily. If you notice any discrepancies between the existing languages, those parts may be compiled but not run, and so you are free to ignore them. - Modify
crates/testing/tests/standalone_integration_test.rsto define new#[test] #[serial]test functions which use your newmodule-test-XXmodule to do the same operations as the existing tests. - Run the tests with
cargo test -p spacetimedb-testing --test standalone_integration_test.