Integration tests
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 unless the crate itself owns persistence.
Cargo’s model
Section titled “Cargo’s model”Cargo compiles each top-level tests/*.rs as its own integration test
binary.
Subdirectories are not compiled as binaries — they hold modules a
top-level entry file pulls in with mod. The standard layout:
crates/nest-rs-authn/├── src/│ ├── error.rs│ ├── jwt/│ │ ├── config.rs│ │ └── service.rs│ └── password/│ └── hash.rs└── tests/ ├── jwt.rs ← one binary, tests/jwt/*.rs modules under it ├── jwt/ │ ├── config.rs │ └── service.rs ├── password.rs ← another binary └── common/ └── mod.rs ← shared helpers, not its own binaryEach tests/*.rs is a normal Rust crate root: declare its modules with
mod, pull in shared helpers from tests/common/mod.rs.
mod common;mod jwt;One file or many?
Section titled “One file or many?”Cargo allows both — pick by build cost and harness coherence:
- One file per concern cluster. A binary per test file means Cargo
links the crate once per file; bundling related tests cuts link time.
Group tests that share setup (e.g. all JWT tests under
tests/jwt.rs). - Separate files for independent concerns. A test suite that does not share helpers with another belongs in its own binary, so a rebuild of one does not relink the other.
- One file is fine for a small crate.
tests/<crate-short>.rswith every module declared inside it is a reasonable default until the build cost shows up.
Shared helpers — tests/common/mod.rs
Section titled “Shared helpers — tests/common/mod.rs”The official Rust idiom
for shared test helpers: put them in tests/common/mod.rs, not
tests/common.rs. The mod.rs form keeps Cargo from compiling them as
their own integration test binary.
pub const DEV_PRIVATE_KEY: &str = include_str!("../fixtures/dev.key");pub const DEV_PUBLIC_KEY: &str = include_str!("../fixtures/dev.pub");use nest_rs_authn::{JwtConfig, JwtKey, JwtService};
#[test]fn into_options_selects_eddsa_from_key_pair() { let options = JwtConfig { private_key: Some(crate::common::DEV_PRIVATE_KEY.into()), public_key: Some(crate::common::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/*.rs is the home for harnesses that
exercise framework wiring across crates — boot-time access-graph
rejection, lifecycle hook ordering, transport contribution. One file
per behaviour cluster, same Cargo conventions.
When a crate owns persistence
Section titled “When a crate owns persistence”If the crate genuinely owns database access (e.g. nest-rs-seaorm, the
SeaORM integration), a real Postgres via
EphemeralDatabase is fair game in
its integration tests. 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.