Skip to content

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.

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 nextest binary(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.rs

tests/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.

crates/nest-rs-authn/tests/integration/main.rs
mod config;
mod service;
mod strategies;

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:

crates/nest-rs-authn/tests/integration/main.rs (abridged)
/// 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 { /* … */ }
crates/nest-rs-authn/tests/integration/config.rs
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");
}

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.

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.

  • 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 test recipe group.