Unit Testing
Test Modules
A test module is a convention in Rust where unit tests and test-only helper functions are grouped inside an inline submodule named tests annotated with #[cfg(test)] at the bottom of a source file.
src/parser.rs
+-------------------------------------------------------------------+
| pub fn parse_header(line: &str) -> Option<(&str, &str)> { ... } |
| fn trim_comment(line: &str) -> &str { ... } |
| |
| #[cfg(test)] <-- Included ONLY when compiling for tests |
| mod tests { |
| use super::*; <-- Imports everything from `parser` module |
| |
| fn sample_input() -> &'static str { ... } // Helper function |
| |
| #[test] |
| fn parses_key_value() { ... } |
| } |
+-------------------------------------------------------------------+
Why #[cfg(test)] Is Essential on mod tests
While individual #[test] functions are automatically omitted during cargo build, any helper functions, mock structs, or use imports inside mod tests are ordinary Rust items. Without #[cfg(test)] on mod tests:
- The compiler spends time type-checking and compiling test helpers and mocks during normal
cargo buildandcargo build --releaseruns. - The compiler emits
dead_codeandunused_importswarnings foruse super::*;and test helper functions when building outside test mode because their only callers (#[test]functions) were stripped. - Any
[dev-dependencies]imported insidemod testswould fail to resolve duringcargo buildbecause dev-dependencies are only linked in test and benchmark builds.
| Aspect | Bare mod tests { ... } |
#[cfg(test)] mod tests { ... } |
|---|---|---|
Compiled during cargo build? |
Yes (except #[test] functions) |
No (entire module is stripped at AST level) |
Triggers unused_imports / dead_code warnings? |
Yes, on helpers and use super::*; |
No |
Can import [dev-dependencies] crates? |
Fails cargo build |
Yes |
Sharing Test Helpers and Fixtures Inside mod tests
Any function inside #[cfg(test)] mod tests that is not marked with #[test] acts as a reusable fixture or assertion helper for the tests in that module:
#[derive(Debug, PartialEq, Eq)]
pub struct Header<'a> {
pub key: &'a str,
pub value: &'a str,
}
pub fn parse_header(line: &str) -> Option<Header<'_>> {
let (k, v) = line.split_once(':')?;
let key = k.trim();
if key.is_empty() {
return None;
}
Some(Header {
key,
value: v.trim(),
})
}
#[cfg(test)]
mod tests {
use super::*;
// Test-only helper (no `#[test]` attribute, so not run as a standalone test)
fn assert_header(raw: &str, expected_key: &str, expected_val: &str) {
assert_eq!(
parse_header(raw),
Some(Header {
key: expected_key,
value: expected_val,
})
);
}
#[test]
fn parses_standard_http_headers() {
assert_header("Content-Type: application/json", "Content-Type", "application/json");
assert_header("X-Request-Id : abc-123 ", "X-Request-Id", "abc-123");
}
#[test]
fn rejects_malformed_headers() {
assert_eq!(parse_header("NoColonHere"), None);
assert_eq!(parse_header(" : value_without_key"), None);
}
}
fn main() {
println!("Parsed: {:?}", parse_header("Host: example.com"));
}
Parsed: Some(Header { key: "Host", value: "example.com" })