Rust Testing
Unit Tests
Tests live alongside the code
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, 1), 0);
}
#[test]
#[should_panic(expected = "overflow")]
fn test_overflow() {
add(i32::MAX, 1); // panics
}
}
[cfg(test)] compiles the module only during testing. [test] marks a function as a test. assert_eq!, assert_ne!, and assert! are the assertion macros.
Running Tests
cargo test commands
cargo test # all tests
cargo test test_add # by name substring
cargo test -- --nocapture # show println! output
cargo test -- --test-threads=1 # run sequentially
cargo test --lib # only unit tests
cargo test --doc # only doc tests
cargo test --test integration # specific integration test file
Tests run in parallel by default. Use --test-threads=1 when tests share resources.
Result-Based Tests
Return Result instead of panicking
#[test]
fn test_parse_config() -> Result<(), Box<dyn std::error::Error>> {
let port: u16 = "8080".parse()?;
assert_eq!(port, 8080);
Ok(())
}
Returning Result lets you use ? in tests. The test fails if Err is returned.
Integration Tests
tests/ directory — test the public API
// tests/integration_test.rs
use netcheck::scanner;
#[test]
fn test_scan_localhost() {
let result = scanner::scan("127.0.0.1", &[80, 443]);
assert!(!result.is_empty());
}
Integration tests live in tests/ at the crate root. They can only access public APIs. Each file compiles as a separate crate.
Test Organization
Shared test helpers
// tests/common/mod.rs — shared setup code
pub fn setup_test_env() -> TestEnv {
TestEnv::new()
}
// tests/scanner_test.rs
mod common;
#[test]
fn test_with_setup() {
let env = common::setup_test_env();
// test code...
}
Custom Assertions
Assertion with context
#[test]
fn test_validate_port() {
let port = 8080;
assert!(
port > 0 && port <= 65535,
"port {port} out of valid range [1, 65535]"
);
assert_eq!(
validate_port(0),
Err(ValidationError::OutOfRange),
"port 0 should be rejected"
);
}
The third argument to assert!, assert_eq!, and assert_ne! is a format string displayed on failure.
Doc Tests
Code in documentation is tested
/// Parses a port number from a string.
///
/// # Examples
///
/// ```
/// use netcheck::parse_port;
///
/// assert_eq!(parse_port("8080"), Ok(8080));
/// assert!(parse_port("abc").is_err());
/// ```
///
/// # Errors
///
/// Returns `Err` if the string is not a valid port number.
pub fn parse_port(s: &str) -> Result<u16, ParseError> {
let port: u16 = s.parse()?;
if port == 0 { return Err(ParseError::Zero); }
Ok(port)
}
Code blocks in doc comments are compiled and run by cargo test --doc. This keeps documentation examples from going stale.