Skip to content

Testing

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/*.rs at the crate root. The crate’s public API, exercised as an external consumer would. Shared fixtures go in tests/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/*.rsCrate’s public APIno
crates/nest-rs-testing/tests/*.rsCross-crate framework wiringtailored
apps/<app>/tests/e2e/main.rsThe app’s real top-level module✓ real
Terminal window
cargo add --dev nest-rs-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
[+] Running container postgres:16-alpine
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)

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 require nestrs run dev <app> + curl against the affected endpoint. If you cannot run the binary, say so explicitly rather than claiming green.
  • No DB mocking in e2e tests — real Postgres via EphemeralDatabase (testcontainers in CI). Unit tests of pure logic need no DB.