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.