Testing Forte Backends
Backend tests in a Forte project are async Rust functions annotated with #[forte_sdk::test]. This macro runs the test inside forte_sdk::runtime::block_on, which is compatible with the WASI async runtime used by Forte components.
#[forte_sdk::test]
Use #[forte_sdk::test] for all async tests in backend crates. Do not use #[tokio::test] — it is not available in wasm32-wasip2.
#[forte_sdk::test]
async fn my_test() {
// async test code
}
The test function must be async fn with no parameters.
Testing with an In-Memory Database
doc_db::memory() returns an in-memory backend with the same Database API as the production Turso/libSQL backend. Each call returns a fresh, isolated instance — no setup, no cleanup, no external server.
use doc_db::DbRequest;
#[forte_sdk::test]
async fn test_user_roundtrip() {
let db = doc_db::memory();
UserPut(User {
id: "alice".into(),
version: 1,
name: "Alice".into(),
email: "[email protected]".into(),
})
.send_with(&db)
.await
.unwrap();
let user: Option<User> = UserGet { id: "alice".into(), version: 1 }
.send_with(&db)
.await
.unwrap();
assert_eq!(user.unwrap().name, "Alice");
}
Two doc_db::memory() calls return independent databases that never share state.
Simulating database errors
The mock API on a memory() database lets you force specific return values or errors. Mocks are consumed one-at-a-time (FIFO) per (op, pk, sk) key; after the mock fires, subsequent calls hit the real in-memory backend.
#[forte_sdk::test]
async fn test_get_error_propagation() {
let db = doc_db::memory();
// Force the next get on this key to fail
db.mock_get("User/id=alice", "version=0000000001")
.returns_err("network timeout");
let result = db.get("User/id=alice", "version=0000000001").await;
assert!(result.is_err());
// Next call hits the real backend (returns None — no data was written)
let result = db.get("User/id=alice", "version=0000000001").await;
assert!(result.unwrap().is_none());
}
See doc-db/overview.md for the full mock API (mock_put, mock_delete, clear_mocks, etc.).
Note on raw pk/sk keys: The mock API takes raw string keys. When using the
#[forte_doc]macro, keys follow the formatTypeName/pk_field=valuefor the pk andsk_field=valuefor the sk. Integer fields are zero-padded (e.g.version=0000000001for au32of 1). Prefer testing through the generated typed helpers (UserGet,UserPut, etc.) and only reach for the raw mock API when you need to simulate errors.
Testing with In-Memory Object Storage
object_storage::memory() returns an in-process Bucket backed by a BTreeMap. The API is identical to production; each call returns a fresh, isolated instance.
#[forte_sdk::test]
async fn test_file_roundtrip() {
let bucket = object_storage::memory();
bucket.put("avatars/alice.png", b"fake png data" as &[u8]).await.unwrap();
let data = bucket.get("avatars/alice.png").await.unwrap();
assert_eq!(data.unwrap().as_ref(), b"fake png data");
bucket.delete("avatars/alice.png").await.unwrap();
assert!(bucket.get("avatars/alice.png").await.unwrap().is_none());
}
Testing Action Handlers Directly
Action and hook handlers are plain async fns. You can call them directly in tests by constructing a ForteRequest manually — there is no constructor, so initialize all fields directly.
use forte_sdk::{ForteRequest, CookieJar};
use forte_sdk::http::{Method, HeaderMap};
#[forte_sdk::test]
async fn test_login_ok() {
let mut jar = CookieJar::new();
let method = Method::POST;
let headers = HeaderMap::new();
let req = ForteRequest {
uri_authority: "localhost",
method: &method,
headers: &headers,
jar: &mut jar,
raw_body: &[],
body: crate::actions::user_login::Input {
email: "[email protected]".to_string(),
password: "correct".to_string(),
},
};
let output = crate::actions::user_login::handler(req).await;
assert!(matches!(output, crate::actions::user_login::Output::Ok { .. }));
}
Combine with doc_db::memory() and object_storage::memory() to test handlers without any external services.
Running Tests
Inside a Forte project (rs/)
A Forte project's rs/ compiles to wasm32-wasip2 (set by .cargo/config.toml). To run tests with cargo test, add the WASM runner to rs/.cargo/config.toml:
[build]
target = "wasm32-wasip2"
[target.wasm32-wasip2]
runner = "forte-test-runner"
Then install the runner and run:
cargo install --path <monorepo>/forte/test-runner
cargo test
Note:
forte initdoes not add the runner automatically. Without it,cargo testwill build the WASM binary but fail to execute it.
From the monorepo workspace root
cargo test # all workspace crates (native targets only)
cargo test -p fn0-doc-db # doc-db only (requires runner + libSQL; see development.md)
cargo test -p forte-sdk # forte-sdk only (native, no runner needed)
doc-db integration tests
doc-db/tests/integration_test.rs connects to a live libSQL server. See development.md for prerequisites (runner installation and starting the local DB).