24.6 Test Organization

24.6.1 Unit Tests

Unit tests are usually placed in the same file or module as the code under test:

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

    #[test]
    fn test_xyz() {
        // ...
    }
}

Benefits:

  • Test Private Functions: You can access private items in the same module.
  • Convenience: Code and tests live side by side.

24.6.2 Integration Tests

Integration tests live in a top-level tests/ directory. Each .rs file there is compiled as a separate crate that imports your library:

my_project/
├── src/
│   └── lib.rs
└── tests/
    ├── test_basic.rs
    └── test_advanced.rs

Inside test_basic.rs:

use my_project; // The name of your crate

#[test]
fn test_something() {
    let result = my_project::some_public_function();
    assert_eq!(result, 42);
}

Integration tests validate public APIs. You can split them across multiple files for clarity.

Common Functionality for Integration Tests

If your integration tests share functionality, you might place common helpers in a file named tests/common/mod.rs, and import them in your test files. Because mod.rs follows a special naming convention, it won’t be treated as a standalone test file.

Running a Single Integration Test File

cargo test --test test_basic

This command runs only the tests in test_basic.rs.

Integration Tests for Binary Crates

If you have only a binary crate (e.g., src/main.rs without src/lib.rs), you cannot directly import functions from main.rs into an integration test. Binary crates produce executables but do not expose APIs to other crates.

A common solution is to move your core functionality into a library (src/lib.rs), letting main.rs handle only top-level execution. This allows you to write standard integration tests against the library crate.