Testing in Rust
Rust has first-class testing support built into the language and toolchain.
Tests live alongside your code, run with cargo test, and the compiler
ensures your test infrastructure compiles correctly alongside your main code.
Unit Tests
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn divide(a: f64, b: f64) -> Result {
if b == 0.0 {
Err("Division by zero".to_string())
} else {
Ok(a / b)
}
}
// Tests module — only compiled when running tests
#[cfg(test)]
mod tests {
use super::*; // Import everything from parent module
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, 1), 0);
}
#[test]
fn test_divide() {
let result = divide(10.0, 2.0).unwrap();
assert!((result - 5.0).abs() < f64::EPSILON);
}
#[test]
fn test_divide_by_zero() {
let result = divide(10.0, 0.0);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Division by zero");
}
#[test]
#[should_panic(expected = "out of bounds")]
fn test_panic() {
let v = vec![1, 2, 3];
let _ = v[99]; // Panics with index out of bounds
}
#[test]
fn test_with_result() -> Result<(), String> {
let result = divide(10.0, 2.0)?;
assert!((result - 5.0).abs() < f64::EPSILON);
Ok(())
}
#[test]
#[ignore] // Skip unless explicitly requested
fn expensive_test() {
// Run with: cargo test -- --ignored
std::thread::sleep(std::time::Duration::from_secs(10));
}
}
Assert Macros
#[cfg(test)]
mod tests {
#[test]
fn demonstrate_asserts() {
// Basic assertions
assert!(true);
assert_eq!(1 + 1, 2); // Left == Right
assert_ne!(1, 2); // Left != Right
// Custom error messages
let age = 17;
assert!(
age >= 18,
"Expected age >= 18, but got {age}"
);
// Floating-point comparison
let result = 0.1 + 0.2;
assert!(
(result - 0.3).abs() < f64::EPSILON,
"Float comparison: {result} != 0.3"
);
// debug_assert! — only in debug builds
debug_assert!(expensive_check(), "Debug-only assertion");
}
fn expensive_check() -> bool { true }
}
Integration Tests
// Integration tests live in tests/ directory
// tests/
// integration_test.rs
// common/
// mod.rs
// --- tests/integration_test.rs ---
use my_crate::add; // Test the public API
#[test]
fn test_public_api() {
assert_eq!(add(10, 20), 30);
}
// --- tests/common/mod.rs ---
// Shared test helpers (not run as a test file)
pub fn setup_test_db() -> TestDb {
// Setup code
TestDb::new()
}
// --- tests/db_test.rs ---
mod common;
#[test]
fn test_with_db() {
let db = common::setup_test_db();
// Test with database
}
// Run specific tests:
// cargo test # All tests
// cargo test test_add # Tests matching "test_add"
// cargo test --test integration # Only integration tests
// cargo test --lib # Only unit tests
// cargo test -- --nocapture # Show stdout output
// cargo test -- --ignored # Run #[ignore] tests
Mocking and Property-Based Testing
// Mocking with mockall
// Cargo.toml: mockall = "0.13"
use mockall::automock;
#[automock]
trait Database {
fn get_user(&self, id: u64) -> Option;
fn save_user(&self, name: &str) -> Result;
}
fn greet_user(db: &impl Database, id: u64) -> String {
match db.get_user(id) {
Some(name) => format!("Hello, {name}!"),
None => "User not found".to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use mockall::predicate::*;
#[test]
fn test_greet_existing_user() {
let mut mock = MockDatabase::new();
mock.expect_get_user()
.with(eq(1))
.returning(|_| Some("Alice".to_string()));
assert_eq!(greet_user(&mock, 1), "Hello, Alice!");
}
#[test]
fn test_greet_missing_user() {
let mut mock = MockDatabase::new();
mock.expect_get_user()
.returning(|_| None);
assert_eq!(greet_user(&mock, 999), "User not found");
}
}
// Property-based testing with proptest
// Cargo.toml: proptest = "1"
// use proptest::prelude::*;
//
// proptest! {
// #[test]
// fn test_add_commutative(a in -1000..1000i32, b in -1000..1000i32) {
// assert_eq!(add(a, b), add(b, a));
// }
//
// #[test]
// fn test_add_identity(a in -1000..1000i32) {
// assert_eq!(add(a, 0), a);
// }
// }
Key Takeaways
- ✅ Unit tests live in
#[cfg(test)] mod testswithin the same file - ✅ Integration tests live in the
tests/directory and test the public API - ✅ Use
assert!,assert_eq!,assert_ne!with custom messages - ✅
#[should_panic]tests expected panics;#[ignore]skips slow tests - ✅ Use mockall for mocking traits and proptest for property-based testing