Skip to content

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' preflowIds and in interpolation as {{flow.flowId.key}}
  • steps — Array of steps (see below)
  • tests — Array of test cases. Each has:
  • testId — Name of the test
  • requestmethod, path, optional headers, body, query (values can use {{flow.flowId.key}})
  • expectedResponsestatus, optional body, 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.token or $headers['set-cookie']. Cookies from the response are sent on subsequent steps.
  • dbReadcollection, query (optional; values can use {{flow.x.y}}). capture: paths into the found document, e.g. { "code": "$.code" }.
  • dbWritecollection, query, update ($set). No capture.
  • dbInsertcollection, document. In document values, __now is 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:

  1. Loads the JSON config from the given path
  2. Creates a Vitest describe(config.suiteId, ...)
  3. For each test: runs any preflowIds flows (accumulating captured values and cookies), then runs the test request with interpolated body/query/headers, then asserts res.status and res.body (and optional headers)
  4. Uses extractFromResponse for capture paths like $body.data.token or $headers['set-cookie']
  5. Uses interpolateValue to replace {{flow.flowId.key}} in request and in flow steps (query, update, document)
  6. For dbRead / dbWrite / dbInsert, uses TestHelpers.db to 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() and TestHelpers.afterAll(). In beforeAll: sets up env (e.g. clears mail env so no real email), starts Mongo Memory ReplSet, connects client, gets db, calls createApp({ db, client }) and assigns to TestHelpers.app and TestHelpers.db. In afterAll: 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.