Unit Testing

Testing Private Functions

In Rust, visibility rules are hierarchical: a child module always has access to all private items defined in its ancestor modules. Because #[cfg(test)] mod tests is a child submodule of the module that contains it, unit tests inside mod tests can directly call private functions, construct structs with private fields, and inspect internal state after importing them with use super::*;.

Module `auth` (parent)
  |-- fn hash_rounds(cost: u32) -> u64   (PRIVATE to `auth`)
  |-- pub fn verify_token(...) -> bool   (PUBLIC)
  |
  +-- #[cfg(test)] mod tests (child of `auth`)
        |-- use super::*;
        |-- Can call both `verify_token` AND private `hash_rounds`!

Accessing Private Functions and Private Struct Fields

pub struct RateLimiter {
    // Private fields: external callers cannot read or mutate these directly
    tokens: u32,
    max_tokens: u32,
}

impl RateLimiter {
    pub fn new(max_tokens: u32) -> Self {
        Self {
            tokens: max_tokens,
            max_tokens,
        }
    }

    pub fn try_acquire(&mut self, cost: u32) -> bool {
        if self.can_afford(cost) {
            self.tokens -= cost;
            true
        } else {
            false
        }
    }

    // Private helper method
    fn can_afford(&self, cost: u32) -> bool {
        cost > 0 && self.tokens >= cost
    }
}

// Private free function inside the module
fn compute_refill(current: u32, added: u32, cap: u32) -> u32 {
    current.saturating_add(added).min(cap)
}

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

    #[test]
    fn tests_private_free_function_directly() {
        assert_eq!(compute_refill(8, 5, 10), 10);
        assert_eq!(compute_refill(u32::MAX, 5, 100), 100);
    }

    #[test]
    fn tests_private_method_and_private_fields() {
        let mut limiter = RateLimiter {
            tokens: 5,
            max_tokens: 10,
        };

        assert!(!limiter.can_afford(0));
        assert!(limiter.can_afford(5));
        assert!(!limiter.can_afford(6));

        assert!(limiter.try_acquire(3));
        // Inspect private field `.tokens` directly!
        assert_eq!(limiter.tokens, 2);
    }
}

fn main() {
    let mut limiter = RateLimiter::new(5);
    println!("Acquired 3 tokens: {}", limiter.try_acquire(3));
    println!("Remaining tokens via private helper: {}", compute_refill(2, 0, 5));
}
Acquired 3 tokens: true
Remaining tokens via private helper: 2

Visibility Boundaries Between Sibling and Child Modules

While a child mod tests can access private items in its parent module (super), a parent or sibling module cannot access private items inside a child submodule unless that submodule marks them pub(super) or pub(crate):

Item Location Visibility Keyword Accessible from mod tests in the Same File? Accessible from tests/*.rs Integration Tests?
Same module (super) Private (default) Yes No
Child submodule Private (default) No No
Child submodule pub(super) or pub(crate) Yes No
Crate root (lib.rs) pub Yes Yes

Balancing Private Unit Tests vs Public Contract Tests

Test private helper functions directly when they implement complex, self-contained algorithms (such as binary search partitioning, checksum math, or parser tokenizers) whose edge cases are tedious to trigger through high-level public entrypoints. For straightforward glue functions, prefer testing through the public module API so internal refactoring does not break existing tests.