Testing Basics
10 examples to get you started with Testing - 7 basic and 3 intermediate.
Prerequisites
- Rust 1.97.0 (edition 2024)
- Familiarity with
cargo testfrom Cargo Basics
Basic Examples
1. Your First Unit Test
Tests are functions annotated with #[test] in the same module or a tests/ module.
fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adds_positive() {
assert_eq!(add(2, 2), 4);
}
}#[cfg(test)]compiles tests only forcargo testassert_eq!prints left/right on failure- Tests run in parallel by default
cargo test adds_positivefilters by name
Related: Unit vs Integration Tests - test placement
2. assert!, assert_eq!, assert_ne!
Rust provides macros for boolean and equality checks.
#[test]
fn validates_email() {
let email = "user@example.com";
assert!(email.contains('@'));
assert_ne!(email, "");
assert_eq!(email.split('@').count(), 2);
}assert!fails on false conditionsassert_eq!usesPartialEqand shows diff- Add custom messages:
assert!(x > 0, "x was {x}") - Prefer descriptive test names over message strings
3. should_panic
Verify code panics on invalid input.
#[test]
#[should_panic(expected = "divide by zero")]
fn panics_on_zero() {
let _ = 1 / 0;
}- Use sparingly; prefer
Resultin library code expectedsubstring must match panic payload#[should_panic]without expected matches any panic- Document why panic is the correct contract
4. Result in Tests
Return Result from tests to use ? for setup failures.
#[test]
fn reads_config() -> std::io::Result<()> {
let data = std::fs::read_to_string("Cargo.toml")?;
assert!(data.contains("[package]"));
Ok(())
}Errfails the test likeassert!- Cleaner than nested
unwrapin setup - Works well with
?on parsing and I/O helpers - Still use
assert!for behavioral expectations
5. Running Subsets
Cargo filters tests by name substring.
cargo test
cargo test add_
cargo test --lib
cargo test --test integration_orders--libruns only unit tests insrc/--test NAMEruns one integration test file-- --nocaptureshowsprintln!output-- --test-threads=1for tests sharing global state
6. Test Modules and Visibility
Test private functions from the same crate.
mod parser {
fn parse_id(s: &str) -> Option<u64> {
s.parse().ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_digits() {
assert_eq!(parse_id("42"), Some(42));
}
}
}- Unit tests live in
mod testsbeside code use super::*imports private items- Integration tests only see
pubAPI - Keep tests close to the code they exercise
7. Ignoring and Expensive Tests
Skip slow tests by default; run explicitly in CI nightly.
#[test]
fn fast_case() {
assert_eq!(2 + 2, 4);
}
#[test]
#[ignore]
fn heavy_simulation() {
// long-running
}cargo test -- --ignored#[ignore]excludes test from defaultcargo test- CI can run ignored tests on schedule
- Prefer feature flags over ignore for optional deps
- Document why a test is ignored in a comment
Intermediate Examples
8. Custom Test Harness (lib test)
Libraries can customize test discovery (advanced).
// src/lib.rs
pub fn init() {}
#[cfg(test)]
mod tests {
#[test]
fn smoke() {
super::init();
}
}- Default harness is sufficient for most crates
- Custom harnesses are rare; use integration tests first
- See unstable
testcrate attributes only when required
Related: Test Organization - shared setup
9. Testing with Dev Dependencies
Add test-only crates without shipping them to consumers.
[dev-dependencies]
tempfile = "3"
pretty_assertions = "1"#[test]
fn writes_temp_file() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("out.txt");
std::fs::write(&path, b"ok").unwrap();
assert!(path.exists());
}dev-dependenciesare not transitive- Use
pretty_assertions::assert_eqfor clearer diffs tempfileavoids polluting repo with test artifacts
Related: Mocking & Test Doubles - fakes and mocks
10. Next Steps: Property and Snapshot Tests
Combine unit tests with stronger guarantees.
#[test]
fn reverse_twice_is_identity() {
let v = vec![1, 2, 3];
let mut w = v.clone();
w.reverse();
w.reverse();
assert_eq!(v, w);
}- Property tests generalize many inputs (Property-Based Testing)
- Snapshot tests lock formatted output (Snapshot Testing)
- Async tests need
#[tokio::test](Testing Async Code)
Related: Testing Best Practices - team standards
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.