Testing¶
How the backend integration test infrastructure works.
Overview¶
Approach¶
The backend uses integration tests only (no unit tests). Tests run against the real Express app created by createApp(), with an in-memory MongoDB (mongodb-memory-server) and injected db/client so no real database is needed.
Tests are config-driven: each suite is a Vitest describe block that loads a single JSON config file. The config defines flows (reusable sequences of HTTP and DB steps) and tests (a request plus expected response; tests can run flows first and use captured values). The runner (runConfigSuite) executes steps, forwards cookies, interpolates {{flow.flowId.key}} in requests/bodies, and asserts status and body.
Layout¶
| Path | Purpose |
|---|---|
tests/suites/*.integration.test.ts |
Suite files; each calls runConfigSuite('tests/configs/<name>.json') |
tests/configs/*.json |
Suite configs: suiteId, flows, tests |
tests/runners/runConfigSuite.ts |
Loads config, runs flows and tests, asserts responses |
tests/setup/globalSetup.ts |
Runs once; loads dotenv |
tests/setup/testHelpers.ts |
Starts Mongo Memory Server, creates app with injected db/client, exposes TestHelpers.app and TestHelpers.db |
tests/types/testConfig.types.ts |
TypeScript types for the config JSON |
Suites run in parallel (file parallelism). Each suite file's beforeAll / afterAll connect and tear down the in-memory DB for that suite.
Config format¶
The JSON config (see tests/types/testConfig.types.ts) has:
- suiteId — Name for the describe block
- flows — Array of flows. Each flow has:
- flowId — Referenced by tests'
preflowIdsand in interpolation as{{flow.flowId.key}} - steps — Array of steps (see below)
- tests — Array of test cases. Each has:
- testId — Name of the test
- request —
method,path, optionalheaders,body,query(values can use{{flow.flowId.key}}) - expectedResponse —
status, optionalbody,headers - preflowIds — Optional list of flowIds to run before this test; their captured values are available for interpolation
Step types¶
- http (default) — Sends an HTTP request to
TestHelpers.app. Can have capture: object mapping keys to paths like$body.data.tokenor$headers['set-cookie']. Cookies from the response are sent on subsequent steps. - dbRead —
collection,query(optional; values can use{{flow.x.y}}). capture: paths into the found document, e.g.{ "code": "$.code" }. - dbWrite —
collection,query,update($set). No capture. - dbInsert —
collection,document. In document values,__nowis current Date,__now+MS(e.g.__now+600000) is current Date + milliseconds. Interpolation{{flow.x.y}}is supported.
Expected response¶
- body — Top-level keys must match exactly. For
data, you can use: - data.__keys — Array of key names that must exist and be non-empty strings (e.g.
["accessToken", "jaduSpineToken"]) - data.__arrayKeys — Array of key names that must be arrays
- headers — Optional; values can be string or RegExp for matching.
Runner and setup¶
runConfigSuite¶
Defined in tests/runners/runConfigSuite.ts. It:
- Loads the JSON config from the given path
- Creates a Vitest
describe(config.suiteId, ...) - For each test: runs any
preflowIdsflows (accumulating captured values and cookies), then runs the test request with interpolated body/query/headers, then assertsres.statusandres.body(and optional headers) - Uses extractFromResponse for capture paths like
$body.data.tokenor$headers['set-cookie'] - Uses interpolateValue to replace
{{flow.flowId.key}}in request and in flow steps (query, update, document) - For dbRead / dbWrite / dbInsert, uses
TestHelpers.dbto run the operation
Cookies from the last response are kept and sent on the next HTTP request in the same flow and then for the test request, so login flows can authenticate subsequent calls.
Setup¶
- globalSetup.ts — Runs once before all test files; loads dotenv.
- testHelpers.ts — Exposes
TestHelpers.beforeAll()andTestHelpers.afterAll(). InbeforeAll: sets up env (e.g. clears mail env so no real email), starts Mongo Memory ReplSet, connects client, gets db, callscreateApp({ db, client })and assigns toTestHelpers.appandTestHelpers.db. InafterAll: closes client and stops the memory server.
Each suite file must call beforeAll(() => TestHelpers.beforeAll()) and afterAll(() => TestHelpers.afterAll()) so the app and DB are available for that suite's config.
Coverage exclusions¶
Coverage is collected only from src/**/*.ts and the following are excluded in vitest.config.integration.ts:
| Exclude | Reason |
|---|---|
src/**/*.openapi.ts |
Generated OpenAPI spec glue; no runtime logic to cover |
src/index.ts |
App entry point; exercised implicitly by integration tests via the app |
src/**/*.config.ts |
Config objects (e.g. auth app configs); validated at load, not by test flows |
src/**/*.types.ts |
Type definitions only; no emitted runtime code |
src/lib/jwt.ts |
Compatibility/debug shim; production uses Better Auth token management |
src/models/**/*.ts |
Data-access layer; exercised only indirectly via services in integration tests |
So coverage reflects controller, service, validator, middleware, and lib code that is hit by the integration tests. To change exclusions or thresholds, edit be/vitest.config.integration.ts.