| name | code:rust-test |
| description | Write Rust tests. Unit tests, integration tests, mockall, proptest, async tests, and coverage.
<example>
Context: User needs to add tests
user: "write tests for this module"
</example>
<example>
Context: User needs test patterns
user: "how do I mock a trait in rust"
</example>
|
Rust Testing
Comprehensive guide to testing in Rust.
Unit Tests (Same File)
pub struct Order {
items: Vec<Item>,
discount: Option<Decimal>,
}
impl Order {
pub fn total(&self) -> Decimal {
let subtotal: Decimal = self.items.iter().map(|i| i.price).sum();
match self.discount {
Some(d) => subtotal * (Decimal::ONE - d),
None => subtotal,
}
}
pub fn apply_discount(&mut self, percent: u8) -> Result<(), OrderError> {
if percent > 100 {
return Err(OrderError::InvalidDiscount);
}
self.discount = Some(Decimal::from(percent) / Decimal::from(100));
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_order() -> Order {
Order {
items: vec![Item { price: Decimal::from(100) }],
discount: None,
}
}
#[test]
fn total_sums_item_prices() {
let order = sample_order();
assert_eq!(order.total(), Decimal::from(100));
}
#[test]
fn apply_discount_reduces_total() {
let mut order = sample_order();
order.apply_discount(10).unwrap();
assert_eq!(order.total(), Decimal::from(90));
}
#[test]
fn apply_discount_rejects_invalid_percentage() {
let mut order = sample_order();
let result = order.apply_discount(150);
assert!(matches!(result, Err(OrderError::InvalidDiscount)));
}
}
Integration Tests (Separate Directory)
use myapp::Order;
#[test]
fn order_workflow() {
let mut order = Order::new();
order.add_item(Item::new("Widget", 50));
order.add_item(Item::new("Gadget", 75));
assert_eq!(order.total(), 125);
order.apply_discount(10).unwrap();
assert_eq!(order.total(), 112.50);
}
Test Organization
src/
├── lib.rs
├── order.rs # Contains #[cfg(test)] mod tests
└── user.rs # Contains #[cfg(test)] mod tests
tests/
├── common/
│ └── mod.rs # Shared test utilities
├── order_test.rs # Integration tests
└── user_test.rs
Shared Test Utilities
use myapp::{Order, Item};
pub fn sample_order() -> Order {
Order {
items: vec![Item::new("Widget", 100)],
discount: None,
}
}
mod common;
#[test]
fn test_order() {
let order = common::sample_order();
}
Test Attributes
#[test]
fn basic_test() {
assert!(true);
}
#[test]
#[ignore]
fn slow_test() {
std::thread::sleep(Duration::from_secs(10));
}
#[test]
#[should_panic(expected = "out of range")]
fn panics_on_invalid_input() {
validate(-1);
}
Assertions
assert!(condition);
assert!(condition, "custom message");
assert_eq!(left, right);
assert_ne!(left, right);
assert!(matches!(result, Ok(_)));
assert!(matches!(result, Err(Error::NotFound { .. })));
assert!(matches!(value, Some(x) if x > 10));
Async Tests
#[tokio::test]
async fn async_test() {
let result = fetch_data().await;
assert!(result.is_ok());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn multi_threaded_test() {
}
Mocking with mockall
use mockall::{automock, predicate::*};
#[automock]
trait Database {
fn get_user(&self, id: u64) -> Option<User>;
fn save_user(&self, user: &User) -> Result<(), DbError>;
}
#[test]
fn service_uses_database() {
let mut mock_db = MockDatabase::new();
mock_db
.expect_get_user()
.with(eq(42))
.times(1)
.returning(|_| Some(User { id: 42, name: "Alice".into() }));
let service = UserService::new(mock_db);
let user = service.find(42);
assert_eq!(user.unwrap().name, "Alice");
}
Property-Based Testing with proptest
use proptest::prelude::*;
proptest! {
#[test]
fn parse_roundtrip(s in "[a-z]+") {
let parsed = parse(&s).unwrap();
let serialized = serialize(&parsed);
prop_assert_eq!(s, serialized);
}
}
Commands
cargo test
cargo test test_name
cargo test order::tests
cargo test -- --ignored
cargo test -- --nocapture
cargo test -- --test-threads=1
cargo test --doc
cargo test --test integration_test
Code Coverage
cargo install cargo-tarpaulin
cargo tarpaulin --out Html
cargo install cargo-llvm-cov
cargo llvm-cov --html
CI Configuration
- name: Test
run: cargo test --all-features
- name: Coverage
run: |
cargo install cargo-tarpaulin
cargo tarpaulin --out Xml
Summary
| Command | Purpose |
|---|
cargo test | Run all tests |
cargo test name | Run tests matching name |
cargo test -- --ignored | Run ignored tests |
cargo test -- --nocapture | Show println! output |
cargo tarpaulin | Code coverage |