Test Organization

Rust categorizes tests as unit tests (small, focused, test one module in isolation, can test private interfaces) and integration tests (external to your library, use public API only, test multiple modules together).

Unit Tests

Place unit tests in each file with the code they test, in a tests module annotated with #[cfg(test)].

The Tests Module and #[cfg(test)]

The #[cfg(test)] annotation compiles and runs test code only with cargo test, not cargo build:

pub fn add(left: u64, right: u64) -> u64 {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

Testing Private Functions

Rust allows testing private functions since tests are just Rust code in the same crate:

pub fn add_two(a: u64) -> u64 {
    internal_adder(a, 2)
}

fn internal_adder(left: u64, right: u64) -> u64 {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn internal() {
        let result = internal_adder(2, 2);
        assert_eq!(result, 4);
    }
}

The use super::*; brings parent module items into scope.

Integration Tests

Create integration tests in a tests directory at the project root (alongside src). Each file in tests is compiled as a separate crate.

The tests Directory

Project structure:

adder
├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
    └── integration_test.rs
use adder::add_two;

#[test]
fn it_adds_two() {
    let result = add_two(2);
    assert_eq!(result, 4);
}

Each test file gets its own section in test output. Run specific integration test file:

$ cargo test --test integration_test

Submodules in Integration Tests

Files in tests directory are separate crates and don’t share the same behavior as src files.

For shared helper functions, use subdirectories instead of top-level files:

├── tests
    ├── common
    │   └── mod.rs      // Shared code
    └── integration_test.rs

Use from integration tests:

use adder::add_two;

mod common;

#[test]
fn it_adds_two() {
    common::setup();

    let result = add_two(2);
    assert_eq!(result, 4);
}

Integration Tests for Binary Crates

Binary crates with only src/main.rs cannot have integration tests that import functions with use statements. Structure binary projects with logic in src/lib.rs and a thin src/main.rs that calls the library functions.

Summary

Rust’s testing features support both detailed unit testing and broader integration testing. Unit tests verify individual modules (including private functions), while integration tests validate public API behavior. This testing strategy helps ensure code correctness as you refactor and extend functionality.