mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-05-06 07:26:43 -04:00
Add TESTING.md, which documents some of our testing (#2898)
# Description of Changes From the perspective of a new client SDK or module library developer, with a focus on the SDK test suite. # API and ABI breaking changes N/a - it's internal docs. # Expected complexity level and risk 0 - it's internal docs. # Testing N/a - it's internal docs. --------- Signed-off-by: Tyler Cloutier <cloutiertyler@users.noreply.github.com> Co-authored-by: Tyler Cloutier <cloutiertyler@users.noreply.github.com> Co-authored-by: Mario Montoya <mamcx@elmalabarista.com> Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
This commit is contained in:
+145
@@ -0,0 +1,145 @@
|
||||
# A brief overview of SpacetimeDB testing
|
||||
## From the perspective of a client SDK or module library developer
|
||||
## By pgoldman 2025-06-25, updated 2026-04-14
|
||||
|
||||
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.
|
||||
|
||||
## The SDK tests
|
||||
|
||||
`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 generate` into a client project.
|
||||
- Compile that client project with the newly-generated bindings.
|
||||
- Run the client project as a subprocess, passing the database name or `Identity` in 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.
|
||||
|
||||
### Testing a new module library
|
||||
|
||||
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:
|
||||
|
||||
1. Create `modules/sdk-test-XX` and `modules/sdk-test-connect-disconnect-XX`, where `XX` is some mnemonic for your language.
|
||||
Populate these with module code which defines all of the same tables and reducers
|
||||
as `modules/sdk-test` and `modules/sdk-test-connect-disconnect` respectively.
|
||||
Take care to use the same names, including casing, for tables, columns, indexes, reducers and other database objects.
|
||||
2. Modify `sdks/rust/tests/test.rs` to add an additional call to `declare_tests_with_suffix!` at the bottom,
|
||||
like `declare_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.
|
||||
3. Run the tests with `cargo test -p spacetimedb-sdk --test test`.
|
||||
|
||||
### Testing a new client SDK
|
||||
|
||||
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`:
|
||||
|
||||
1. Find somewhere sensible to define test projects `test-client` and `connect_disconnect_client` for your client SDK language.
|
||||
If your client SDK is in-tree, put these within its directory, following the existing layout under `sdks/rust/tests/` or `sdks/unreal/tests/`.
|
||||
2. Use `spacetime generate` manually, or via the harness, to generate those projects' `module_bindings`.
|
||||
3. Populate those projects with client code
|
||||
matching `sdks/rust/tests/test-client` and `sdks/rust/tests/connect_disconnect_client` respectively.
|
||||
- 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 in `argv[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 `TestCounter` defined in `sdks/rust/tests/test-counter`.
|
||||
This is effectively a semaphore with a timeout.
|
||||
You may or may not need to replicate this behavior.
|
||||
4. Create integration tests in the SDK crate which construct `spacetimedb_testing::sdk::Test` objects,
|
||||
following `sdks/rust/tests/test.rs` or `sdks/unreal/tests/test.rs` as a template.
|
||||
5. Define `#[test]` tests for each test case you have implemented,
|
||||
which construct `spacetimedb_testing::sdk::Test` objects containing the various subcommand strings to run your client project,
|
||||
then call `.run()` on them.
|
||||
|
||||
### Adding a new test case
|
||||
|
||||
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:
|
||||
|
||||
1. If necessary, add new tables and/or reducers to `modules/sdk-test` and friends which exercise the behavior you want to test.
|
||||
2. Add a new function, `exec_foo`, to the appropriate client project,
|
||||
such as `sdks/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.
|
||||
3. Add a branch to that client's dispatch logic,
|
||||
such as the `match` in `sdks/rust/tests/test-client/src/main.rs`,
|
||||
which matches the test name `foo` and dispatches to call your `exec_foo` function.
|
||||
4. Add a `#[test]` test function to the relevant test driver,
|
||||
such as `sdks/rust/tests/test.rs`,
|
||||
which does `make_test("foo").run()`, where `"foo"` is the test name you chose in step 3.
|
||||
5. Repeat steps 2 through 4 for any other client projects which ought to cover the same behavior.
|
||||
6. Run the new test with the relevant `cargo test` command for that SDK.
|
||||
|
||||
## Schema parity tests
|
||||
|
||||
`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.
|
||||
|
||||
## The smoketests
|
||||
|
||||
`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.
|
||||
|
||||
## Standalone integration test
|
||||
|
||||
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:
|
||||
|
||||
1. Create `modules/module-test-XX`, where `XX` is some mnemonic for your language.
|
||||
2. Populate this with module code which defines all of the same tables and reducers
|
||||
as the existing `module-test` family.
|
||||
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.
|
||||
3. Modify `crates/testing/tests/standalone_integration_test.rs` to define new `#[test] #[serial]` test functions
|
||||
which use your new `module-test-XX` module to do the same operations as the existing tests.
|
||||
4. Run the tests with `cargo test -p spacetimedb-testing --test standalone_integration_test`.
|
||||
Reference in New Issue
Block a user