Skip to content

Module unit testing - #5558

Open
jsdt wants to merge 45 commits into
masterfrom
jsdt/portable-datastore-2
Open

jsdt wants to merge 45 commits into
masterfrom
jsdt/portable-datastore-2

Conversation

@jsdt

@jsdt jsdt commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

Description of Changes

Adds unit testing support for SpacetimeDB module code in Rust and TypeScript.

The main work here is adding a test datastore API for use in these unit tests. There are some new feature gates added for this, which are described below.

Module Unit Test APIs

  • Adds spacetimedb::test_utils for Rust module tests, including TestContext, TestAuth, reducer transactions, procedure/view contexts, typed queries, test clock/RNG helpers, and HTTP/sleep hooks.
  • Adds TypeScript server test utilities under spacetimedb/server/test-utils, with Vitest and WASM runtime helpers.
  • Refactors generated/runtime hooks so Rust and TypeScript module code can run against an in-memory test datastore instead of host syscalls.

Portable Datastore

  • Adds spacetimedb-test-datastore, a native in-memory datastore wrapper for Rust module tests.
  • Adds spacetimedb-portable-datastore, a WASM-compatible in-memory datastore initialized from a module definition.
  • Adds spacetimedb-portable-datastore-wasm, a thin wasm-bindgen adapter used by the TypeScript test runtime.
  • Splits spacetimedb-datastore behind feature gates so portable builds can avoid server-only dependencies:
    • durability: commitlog, snapshot, durability, and path-dependent code.
    • execution: execution-plan-aware transaction/query behavior.
    • metrics: Prometheus/SpacetimeDB metrics plumbing.
    • portable: minimal datastore surface needed by the portable test datastore.
  • Adds wasm32-unknown-unknown guards, including portable time handling where std::time::Instant::now() is unavailable.

Test Datastore ABI

For Rust module tests, look at:

  • crates/bindings/src/test_utils.rs
  • crates/test-datastore/src/lib.rs
  • crates/bindings/src/table.rs

For TypeScript module tests, look at:

  • crates/portable-datastore-wasm/src/lib.rs for the wasm-bindgen ABI.
  • crates/bindings-typescript/src/server/test-utils/wasm.ts for the TypeScript-side ABI interface.
  • crates/bindings-typescript/src/server/test-utils/backend.ts and crates/bindings-typescript/src/server/backend.ts for the DatastoreBackend bridge.

Example Usage

Examples are included in the updated chat templates (though I need to either remove these or replace them with tests that are actually meaningful for the templates):

  • Rust: templates/chat-console-rs/spacetimedb/src/lib.rs

    • TestContext::new()
    • with_reducer_tx
    • TestAuth::from_jwt_payload
    • procedure_context
    • run_query
    • mocked procedure HTTP
  • TypeScript: templates/chat-react-ts/spacetimedb/src/index.test.ts

    • createModuleTestHarness(spacetime, moduleExports)
    • test.withReducerTx
    • test.procedureContext
    • test.viewContext
    • test.runQuery

For exhaustive coverage, see:

  • crates/bindings/tests/test_utils.rs
  • crates/bindings-typescript/tests/server_test_utils_wasm.test.ts

Testing

New test coverage added in this branch:

  • Rust module unit-test API coverage in crates/bindings/tests/test_utils.rs
  • Native test datastore coverage in crates/test-datastore/src/lib.rs
  • Portable datastore coverage in crates/portable-datastore/src/lib.rs
  • TypeScript/WASM module test harness coverage in crates/bindings-typescript/tests/server_test_utils_wasm.test.ts
  • TypeScript WASM adapter coverage in crates/bindings-typescript/tests/server_test_utils_wasm_adapter.test.ts
  • Template example tests in:
    • templates/chat-console-rs/spacetimedb/src/lib.rs
    • templates/chat-react-ts/spacetimedb/src/index.test.ts

Adds cargo ci portable-datastore, wired into GitHub Actions as the portable_datastore job. This checks that the portable datastore path stays buildable for wasm32-unknown-unknown without the normal server-only dependency stack, then runs native portable datastore tests.

jsdt added 25 commits April 30, 2026 12:56
…tore

# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	crates/bindings-typescript/src/server/procedures.ts
#	crates/bindings-typescript/src/server/runtime.ts
#	crates/bindings-typescript/vitest.config.ts
#	crates/bindings/src/http.rs
#	crates/bindings/src/lib.rs
#	crates/bindings/src/rt.rs
#	crates/bindings/tests/ui/views.stderr
#	crates/datastore/src/locking_tx_datastore/committed_state.rs
#	crates/datastore/src/locking_tx_datastore/datastore.rs
#	crates/datastore/src/locking_tx_datastore/mut_tx.rs
#	tools/ci/src/main.rs
@jsdt
jsdt requested a review from gefjon July 20, 2026 15:11
@jsdt
jsdt force-pushed the jsdt/portable-datastore-2 branch from 225987b to 72010c1 Compare September 14, 2026 06:21
@jsdt
jsdt requested review from Shubham8287 and removed request for gefjon September 14, 2026 21:56
@jsdt
jsdt marked this pull request as ready for review September 16, 2026 15:08
@@ -1,3 +1,5 @@
#![cfg_attr(not(feature = "metrics"), allow(dead_code, unused_imports, unused_variables))]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ca we not avoid adding lint supressors at file level?

@@ -1,11 +1,18 @@
#![cfg_attr(not(feature = "metrics"), allow(dead_code))]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

#vis struct #index_ident;
impl spacetimedb::table::#index_kind_trait for #index_ident {}
impl spacetimedb::table::Index for #index_ident {
const INDEX_NAME: &'static str = #index_name;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not clear to me, why is this added. Maybe put a comment that it is required for Test setup and how Host doesn't use it.

Comment thread crates/bindings-sys/src/lib.rs Outdated
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
pub fn datastore_table_row_count(table_id: TableId, out: *mut u64) -> u16;
// pub fn datastore_table_row_count<T: InstanceId>(table_id: TableId, out: *mut u64, instance_id: T) -> u16;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove it?

}
}

#[cfg(feature = "execution")]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the consequences of messing up with feature flags in future? I am worried, what happen if I make a new change in this file and forget to put the cfg.
Will it be caught by build/CI?


function makeModule() {
const person = table(
{ name: 'person', public: true },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have a test which exercise case conversion too, such that some names defined in other than snake case inside the module.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants