Integration Testing

Integration Test Organization

Because Cargo compiles every top-level .rs file in tests/ into its own separate executable binary, a project with 25 top-level tests/*.rs files must link 25 distinct test binaries during cargo test. Organizing integration tests into a single unified integration test crate (or a small number of domain crates) with submodules dramatically reduces linking time and binary disk usage.

Anti-Pattern (Many Separate Link Steps):      Recommended Pattern (Single Unified Binary):
tests/                                        tests/
├── auth_test.rs       (Binary #1)            └── integration/
├── billing_test.rs    (Binary #2)                ├── main.rs        (Single Binary!)
├── cache_test.rs      (Binary #3)                ├── common.rs      (Shared helpers)
├── config_test.rs     (Binary #4)                ├── auth_test.rs   (`mod auth_test;`)
└── webhook_test.rs    (Binary #5)                ├── billing_test.rs(`mod billing_test;`)
                                                  └── cache_test.rs  (`mod cache_test;`)
                                              (or `tests/integration.rs` + `tests/integration/*.rs`)

How Cargo Treats Top-Level Files vs Subdirectories in tests/

Cargo follows a strict directory rule inside tests/:

  1. Top-level .rs files (tests/api.rs): Compiled as standalone integration test binaries.
  2. Subdirectories (tests/common/mod.rs or tests/api/auth.rs): Never compiled as standalone binaries on their own; they are only compiled when included via mod ...; from a top-level test file (or when tests/<name>/main.rs is used in Cargo's multi-file test package layout).
Layout Number of Linked Test Binaries dead_code Warnings on Shared Helpers? Parallel Execution Across All Integration Tests?
Many tests/*.rs files $N$ binaries (slow linking) Yes, if a file doesn't use every helper in common Sequential between binaries; parallel only within each binary
Single tests/integration.rs + tests/integration/*.rs submodules 1 binary (fast linking) No (all submodules share the same crate) Yes (all integration tests run in one shared thread pool)

Structuring a Single-Binary Integration Test Suite

Create a single entry file tests/integration.rs that declares your domain test submodules located in tests/integration/:

my_crate/
├── Cargo.toml
├── src/
│   └── lib.rs
└── tests/
    ├── integration.rs          <-- The ONLY top-level `.rs` file in `tests/`
    └── integration/
        ├── helpers.rs          <-- Shared test fixtures
        ├── auth.rs             <-- `mod auth;`
        └── routing.rs          <-- `mod routing;`

Inside tests/integration.rs:

// Simulating `tests/integration.rs` with submodules `helpers`, `auth`, and `routing`
mod helpers {
    pub fn test_endpoint(path: &str) -> String {
        format!("http://127.0.0.1:8080{path}")
    }
}

mod auth {
    use super::helpers;

    #[test]
    fn login_endpoint_is_well_formed() {
        let url = helpers::test_endpoint("/v1/login");
        assert!(url.ends_with("/v1/login"));
    }
}

mod routing {
    use super::helpers;

    #[test]
    fn health_endpoint_is_well_formed() {
        let url = helpers::test_endpoint("/healthz");
        assert!(url.starts_with("http://127.0.0.1:8080"));
    }
}

fn main() {
    println!("Sample endpoint: {}", helpers::test_endpoint("/status"));
}
Sample endpoint: http://127.0.0.1:8080/status

When you run cargo test --test integration, all tests across auth and routing compile in a single link pass and execute in parallel:

     Running tests/integration.rs (target/debug/deps/integration-1a2b3c4d)

running 2 tests
test auth::login_endpoint_is_well_formed ... ok
test routing::health_endpoint_is_well_formed ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s