Skip to content

Testing

Boot the real DI graph in-process, drive every transport in cargo test, override providers with first-class support.

nest-rs-testing boots the real AppBuilder (the four-phase collect → factories → register → access-graph check) in-process, configures an HttpTransport without a socket, and exposes a typed client over poem::test::TestClient. GraphQL, OpenAPI, and MCP ride the same client because they self-mount as HTTP endpoints.

The principle: a test is the truth of what production does. No mocked DI graph, no separate test wiring.

Three categories, Rust’s standard model plus the app e2e:

  • Unit tests#[cfg(test)] mod tests inside src/. Pure logic next to the code under test, with private-item access and no DI boot.
  • Integration teststests/integration/main.rs, a directory and never a flat tests/<x>.rs. The crate’s public API, exercised as an external consumer would. Shared fixtures go in tests/integration/common/mod.rs.
  • End-to-end testsTestApp boots the real AppModule against an EphemeralDatabase. The route table, the DI graph, the access-graph check, authn/authz, and the data path all run.
WhereWhat it testsBoots?
crates/<crate>/src/**.rs (#[cfg(test)])In-file logic, private itemsno
crates/<crate>/tests/integration/main.rsCrate’s public APIno
crates/<crate>/tests/e2e/main.rsCrate’s public API against live infratailored
apps/<app>/tests/e2e/main.rsThe app’s real top-level module✓ real
Terminal window
cargo add --dev nest-rs --features testing

A dev-dependency — the harness only compiles under cargo test, never into your release binary.

The three categories are the where. The which depends on the risk you carry. nestrs ships techniques for each — pick what matches, skip what doesn’t:

Risk you carryTechnique it earns
Wiring (DI graph, routes mounted, guards bound)E2E — TestApp boots the real module tree
Security policy regressions (Ability::mask, condition_for, WireModelDefaults)Policy tests — pure unit tests on the policy types
Failure modes (401 / 403 / 400 / rollback)Negative-path tests in the app’s e2e
Pure logic — calculations, conversions, validatorsUnit tests next to the code
Edge cases in parsers and validatorsProperty-based testing with proptest
API contract drift (OpenAPI, GraphQL SDL)Snapshot tests on /api-json and schema.graphql
A framework crate’s public APIIntegration tests in the crate’s tests/

A small CRUD feature with no calculation and no custom policy can ship with zero unit tests, zero integration tests, and full coverage through its app’s e2e. That is a valid outcome — write what the risk earns, skip what it doesn’t.

Terminal window
$ nestrs run test unit # unit + integration, no DB
Compiling api v0.1.0
Finished `test` profile in 12.4s
test result: ok. 142 passed in 0.21s
Terminal window
$ nestrs run test e2e # e2e — Postgres required
test create_then_list_users ... ok
test result: ok. 14 passed in 4.31s
Terminal window
$ nestrs run test cov # full suite with coverage (llvm-cov)
error: failed to find llvm-tools-preview, please install llvm-tools-preview,
or set LLVM_COV and LLVM_PROFDATA environment variables

cov is the one recipe with a prerequisite the CLI does not bootstrap: it shells out to the LLVM tools, installed with rustup component add llvm-tools-preview. A toolchain without rustup (Homebrew, a distro package) points LLVM_COV / LLVM_PROFDATA at its own copies instead. unit, e2e and doc need nothing beyond the bootstrap.

The split is by what each command needs to run, not by Rust test category. nestrs run test unit covers everything that runs without Postgres — unit tests and integration tests of crates that don’t own persistence. nestrs run test e2e covers the rest. The full suite is nestrs run test unit && nestrs run test e2e.

A wiring bug does not surface in a unit test.

  • Every app ships a tests/e2e/main.rs booting its real top-level module — it proves the route table, the DI graph, the access-graph check, the authn/authz layers, and the data path are all wired.
  • HTTP/GraphQL changes are confirmed against a running binary: nestrs run dev <app> plus a curl on the affected endpoint. The e2e suite proves the wiring; the curl proves the wire shape.
  • No DB mocking in e2e tests — a real database per run via EphemeralDatabase, created on the server NESTRS_SEAORM__URL points at and dropped when the guard drops. Unit tests of pure logic need no DB.
  • Unit tests — pure logic next to the code, no DI boot.
  • Integration tests — a crate’s public API in process, no database and no network.
  • End-to-end tests — the real AppModule against live Postgres, Redis and S3.
  • Negative-path tests — auth refused, cross-tenant denied, validation failed, rollback verified.
  • Policy tests — asserting on Ability::mask and condition_for directly, because a regression there is a leak.
  • The Publish workspace — the exemplar apps these suites ship against.
  • CLInestrs run test, and the nextest gate that keeps e2e out of the fast loop.
  • Overriding in tests — hermetic configuration in both suites.