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 testsinsidesrc/. Pure logic next to the code under test, with private-item access and no DI boot. - Integration tests —
tests/integration/main.rs, a directory and never a flattests/<x>.rs. The crate’s public API, exercised as an external consumer would. Shared fixtures go intests/integration/common/mod.rs. - End-to-end tests —
TestAppboots the realAppModuleagainst anEphemeralDatabase. The route table, the DI graph, the access-graph check, authn/authz, and the data path all run.
| Where | What it tests | Boots? |
|---|---|---|
crates/<crate>/src/**.rs (#[cfg(test)]) | In-file logic, private items | no |
crates/<crate>/tests/integration/main.rs | Crate’s public API | no |
crates/<crate>/tests/e2e/main.rs | Crate’s public API against live infra | tailored |
apps/<app>/tests/e2e/main.rs | The app’s real top-level module | ✓ real |
Install
Section titled “Install”cargo add --dev nest-rs --features testingA dev-dependency — the harness only compiles under cargo test, never into
your release binary.
Picking the right tool
Section titled “Picking the right tool”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 carry | Technique 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, validators | Unit tests next to the code |
| Edge cases in parsers and validators | Property-based testing with proptest |
| API contract drift (OpenAPI, GraphQL SDL) | Snapshot tests on /api-json and schema.graphql |
| A framework crate’s public API | Integration 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.
Run it
Section titled “Run it”$ nestrs run test unit # unit + integration, no DB Compiling api v0.1.0 Finished `test` profile in 12.4stest result: ok. 142 passed in 0.21s$ nestrs run test e2e # e2e — Postgres requiredtest create_then_list_users ... oktest result: ok. 14 passed in 4.31s$ 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 variablescov 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.
What “done” requires
Section titled “What “done” requires”A wiring bug does not surface in a unit test.
- Every app ships a
tests/e2e/main.rsbooting 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 acurlon 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 serverNESTRS_SEAORM__URLpoints at and dropped when the guard drops. Unit tests of pure logic need no DB.
Reference
Section titled “Reference”crates/nest-rs-testing/—TestApp,TestAppBuilder,EphemeralDatabase, the in-process HTTP client.apps/api/tests/e2e/main.rsandapps/auth/tests/e2e/main.rs— the live baseline tests.- The Rust Book, ch. 11.3 and the Cargo Book on integration tests — the underlying conventions.
In this section
Section titled “In this section”Basics
Section titled “Basics”- 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
AppModuleagainst live Postgres, Redis and S3.
All options
Section titled “All options”- Negative-path tests — auth refused, cross-tenant denied, validation failed, rollback verified.
- Policy tests — asserting on
Ability::maskandcondition_fordirectly, because a regression there is a leak.
Going further
Section titled “Going further”- The Publish workspace — the exemplar apps these suites ship against.
- CLI —
nestrs run test, and the nextest gate that keeps e2e out of the fast loop. - Overriding in tests — hermetic configuration in both suites.