Integration tests
The crate's public API in process — one binary at tests/integration/main.rs, submodules mirroring src/, shared fixtures alongside it.
A crate integration test exercises the crate’s public API in process: it constructs types by hand, calls them, and asserts on the result. No app boot, no database, no network.
One layout, two suite names
Section titled “One layout, two suite names”A test binary is always the directory form tests/<suite>/main.rs,
and <suite> is one of exactly two canonical names:
integration— in-process tests of the public API (this page);e2e— tests that need live infrastructure, gated by the nextestbinary(e2e)filter (see End-to-end tests).
Flat tests/<x>.rs files don’t exist in this codebase: Cargo compiles
each one as its own binary, so a suite scattered across sibling
files escapes the binary(e2e) gate and relinks once per file. The
directory form answers the layout question once, for every crate:
crates/nest-rs-authn/├── src/│ ├── config.rs│ ├── service.rs│ └── strategies/│ └── jwt.rs└── tests/ └── integration/ ← one binary named `integration` ├── main.rs ← //! + mod lines + shared fixtures ├── config.rs ← tests src/config.rs ├── service.rs ← tests src/service.rs └── strategies/ ├── mod.rs ← mod jwt; └── jwt.rs ← tests src/strategies/jwt.rstests/integration/main.rs is the crate root of the integration
binary; mod strategies; resolves to
tests/integration/strategies/mod.rs, and a single-file module needs no
mod.rs (mod service; → tests/integration/service.rs). Nothing under
tests/integration/ is auto-discovered as a second binary.
Inside the suite, the module tree mirrors src/ — the test for
src/strategies/jwt.rs lives at tests/integration/strategies/jwt.rs,
so “where is this asserted?” has the same answer as “where is this
implemented?”. main.rs is the suite root, never a test module: a
//! header, the mod list, and the fixtures the siblings share. No
#[test] function lives there.
mod config;mod service;mod strategies;Shared helpers
Section titled “Shared helpers”Shared helpers live at the suite root — in main.rs itself, reached
from every sibling module as crate::…. That is what makes main.rs the
root rather than one more test module, and it is why no common/ folder
appears above:
/// Ed25519 key pair used across nestrs dev and e2e apps.pub const DEV_PRIVATE_KEY: &str = "-----BEGIN PRIVATE KEY-----\n…\n";pub const DEV_PUBLIC_KEY: &str = "-----BEGIN PUBLIC KEY-----\n…\n";
pub fn request(headers: &[(&str, &str)]) -> Request { /* … */ }use nest_rs::authn::{JwtConfig, JwtKey, JwtService};
#[test]fn into_options_selects_eddsa_from_key_pair() { let options = JwtConfig { private_key: Some(crate::DEV_PRIVATE_KEY.into()), public_key: Some(crate::DEV_PUBLIC_KEY.into()), ..Default::default() } .into_options() .expect("options"); assert!(matches!(options.key, JwtKey::Pem { .. })); JwtService::new(options).expect("EdDSA service builds");}Cross-crate framework wiring
Section titled “Cross-crate framework wiring”crates/nest-rs-testing/tests/integration/ is the home for harnesses
that exercise framework wiring across crates — boot-time access-graph
rejection, lifecycle hook ordering, transport contribution. Same
layout; one module per behaviour cluster instead of a src/ mirror,
since the crate under test is the framework itself.
When a crate needs live infrastructure
Section titled “When a crate needs live infrastructure”If the crate genuinely owns a live backend (e.g. nest-rs-seaorm
against Postgres, nest-rs-storage against an S3 server), those tests
move to the crate’s tests/e2e/main.rs suite — same directory
form, gated out of nestrs run test unit by the binary(e2e) filter,
never by #[ignore]. A test that mocks the database to assert query
shape belongs deleted, not rewritten — the queries are exercised
end-to-end against a real engine.
Going further
Section titled “Going further”- End-to-end tests — boot the real AppModule against live Postgres.
- Unit tests — pure logic next to the code, no DI boot.
- Testing — the three categories and the
testrecipe group.