
Rust Testing
- 5.8k installs
- 234k repo stars
- Updated July 27, 2026
- affaan-m/everything-claude-code
Systematic patterns for writing reliable Rust tests including unit, integration, async, property-based, and doc tests following TDD methodology with coverage tracking.
About
Rust testing patterns skill covers comprehensive testing strategies including unit tests in cfg(test) modules, integration tests in separate binaries, async testing with tokio, property-based testing with proptest, and mocking with mockall. Developers use this when implementing new functions, adding coverage to existing code, or following TDD workflows. Key workflows include the RED-GREEN-REFACTOR cycle, parameterized tests with rstest, doc tests for executable documentation, benchmarking with criterion, and coverage analysis with cargo-llvm-cov targeting 80%+ coverage on general code. --- name: rust-testing description: Rust testing patterns including unit tests, integration tests, async testing, property-based testing, mocking, and coverage. origin: ECC --- # Rust Testing Patterns Comprehensive Rust testing patterns for writing reliable, maintainable tests following TDD methodology. ## When to Use - Writing new Rust functions, methods, or traits - Adding test coverage to existing code - Creating benchmarks for performance-critical code - Implementing property-based tests for input validation - Following TDD workflow in Rust projects ## How It Works 1.
- RED-GREEN-REFACTOR TDD cycle: write failing test first, implement minimal code to pass, refactor while keeping tests gre
- Unit tests in #[cfg(test)] modules with assertion macros (assert_eq!, assert!, matches!); integration tests as separate
- Async testing with #[tokio::test], timeout handling, and Result-based test functions for cleaner error reporting
- Property-based testing with proptest strategies and parameterized tests with rstest fixtures for input validation covera
- Mocking with mockall #[automock] for trait-based isolation, benchmarking with criterion, and coverage reporting via carg
Rust Testing by the numbers
- 5,761 all-time installs (skills.sh)
- +341 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #4 of 129 Rust skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
rust-testing capabilities & compatibility
- Capabilities
- write and organize unit tests in cfg(test) modul · implement integration tests as separate test bin · test async code with #[tokio::test] and timeout · create property based tests with proptest strate · mock traits and dependencies with mockall automo · benchmark performance critical code with criteri · measure and report test coverage with cargo llvm · follow red green refactor tdd cycle
- Works with
- github
- Use cases
- testing · debugging · code review
- Platforms
- macOS · Windows · Linux · WSL
- Runs
- Runs locally
- Pricing
- Free
npx skills add https://github.com/affaan-m/everything-claude-code --skill rust-testingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5.8k |
|---|---|
| repo stars | ★ 234k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | affaan-m/everything-claude-code ↗ |
What it does
Write unit, integration, async, and property-based tests for Rust code following TDD methodology with coverage tracking.
Who is it for?
Backend developers, library authors, systems programmers, and teams implementing TDD workflows in Rust projects with coverage requirements.
Skip if: Frontend-only UI testing, E2E browser automation, performance profiling without benchmarking context, or quick prototypes skipping test infrastructure.
When should I use this skill?
Starting new Rust functions or modules; adding test coverage to existing code; implementing benchmarks for performance-critical sections; validating input with property tests; following TDD workflow.
What you get
Developers write maintainable test suites following TDD, achieve 80%+ code coverage, validate behavior across unit/integration/async scenarios, and confidently refactor with test safety nets.
- Test suite covering unit, integration, and edge cases
- Property-based tests validating input constraints
- Benchmarks for performance-critical code
By the numbers
- Coverage target: 80%+ for general code, 90%+ for public APIs, 100% for critical business logic
- Assertion macros: assert_eq!, assert_ne!, assert!, matches! for different comparison patterns
- Testing crates: rstest for parameterized tests, proptest for property-based, mockall for mocking, criterion for benchmar
Files
Rust Testing Patterns
Comprehensive Rust testing patterns for writing reliable, maintainable tests following TDD methodology.
When to Use
- Writing new Rust functions, methods, or traits
- Adding test coverage to existing code
- Creating benchmarks for performance-critical code
- Implementing property-based tests for input validation
- Following TDD workflow in Rust projects
How It Works
1. Identify target code — Find the function, trait, or module to test 2. Write a test — Use #[test] in a #[cfg(test)] module, rstest for parameterized tests, or proptest for property-based tests 3. Mock dependencies — Use mockall to isolate the unit under test 4. Run tests (RED) — Verify the test fails with the expected error 5. Implement (GREEN) — Write minimal code to pass 6. Refactor — Improve while keeping tests green 7. Check coverage — Use cargo-llvm-cov, target 80%+
TDD Workflow for Rust
The RED-GREEN-REFACTOR Cycle
RED → Write a failing test first
GREEN → Write minimal code to pass the test
REFACTOR → Improve code while keeping tests green
REPEAT → Continue with next requirementStep-by-Step TDD in Rust
// RED: Write test first, use todo!() as placeholder
pub fn add(a: i32, b: i32) -> i32 { todo!() }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() { assert_eq!(add(2, 3), 5); }
}
// cargo test → panics at 'not yet implemented'// GREEN: Replace todo!() with minimal implementation
pub fn add(a: i32, b: i32) -> i32 { a + b }
// cargo test → PASS, then REFACTOR while keeping tests greenUnit Tests
Module-Level Test Organization
// src/user.rs
pub struct User {
pub name: String,
pub email: String,
}
impl User {
pub fn new(name: impl Into<String>, email: impl Into<String>) -> Result<Self, String> {
let email = email.into();
if !email.contains('@') {
return Err(format!("invalid email: {email}"));
}
Ok(Self { name: name.into(), email })
}
pub fn display_name(&self) -> &str {
&self.name
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn creates_user_with_valid_email() {
let user = User::new("Alice", "alice@example.com").unwrap();
assert_eq!(user.display_name(), "Alice");
assert_eq!(user.email, "alice@example.com");
}
#[test]
fn rejects_invalid_email() {
let result = User::new("Bob", "not-an-email");
assert!(result.is_err());
assert!(result.unwrap_err().contains("invalid email"));
}
}Assertion Macros
assert_eq!(2 + 2, 4); // Equality
assert_ne!(2 + 2, 5); // Inequality
assert!(vec![1, 2, 3].contains(&2)); // Boolean
assert_eq!(value, 42, "expected 42 but got {value}"); // Custom message
assert!((0.1_f64 + 0.2 - 0.3).abs() < f64::EPSILON); // Float comparisonError and Panic Testing
Testing Result Returns
#[test]
fn parse_returns_error_for_invalid_input() {
let result = parse_config("}{invalid");
assert!(result.is_err());
// Assert specific error variant
let err = result.unwrap_err();
assert!(matches!(err, ConfigError::ParseError(_)));
}
#[test]
fn parse_succeeds_for_valid_input() -> Result<(), Box<dyn std::error::Error>> {
let config = parse_config(r#"{"port": 8080}"#)?;
assert_eq!(config.port, 8080);
Ok(()) // Test fails if any ? returns Err
}Testing Panics
#[test]
#[should_panic]
fn panics_on_empty_input() {
process(&[]);
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn panics_with_specific_message() {
let v: Vec<i32> = vec![];
let _ = v[0];
}Integration Tests
File Structure
my_crate/
├── src/
│ └── lib.rs
├── tests/ # Integration tests
│ ├── api_test.rs # Each file is a separate test binary
│ ├── db_test.rs
│ └── common/ # Shared test utilities
│ └── mod.rsWriting Integration Tests
// tests/api_test.rs
use my_crate::{App, Config};
#[test]
fn full_request_lifecycle() {
let config = Config::test_default();
let app = App::new(config);
let response = app.handle_request("/health");
assert_eq!(response.status, 200);
assert_eq!(response.body, "OK");
}Async Tests
With Tokio
#[tokio::test]
async fn fetches_data_successfully() {
let client = TestClient::new().await;
let result = client.get("/data").await;
assert!(result.is_ok());
assert_eq!(result.unwrap().items.len(), 3);
}
#[tokio::test]
async fn handles_timeout() {
use std::time::Duration;
let result = tokio::time::timeout(
Duration::from_millis(100),
slow_operation(),
).await;
assert!(result.is_err(), "should have timed out");
}Test Organization Patterns
Parameterized Tests with rstest
use rstest::{rstest, fixture};
#[rstest]
#[case("hello", 5)]
#[case("", 0)]
#[case("rust", 4)]
fn test_string_length(#[case] input: &str, #[case] expected: usize) {
assert_eq!(input.len(), expected);
}
// Fixtures
#[fixture]
fn test_db() -> TestDb {
TestDb::new_in_memory()
}
#[rstest]
fn test_insert(test_db: TestDb) {
test_db.insert("key", "value");
assert_eq!(test_db.get("key"), Some("value".into()));
}Test Helpers
#[cfg(test)]
mod tests {
use super::*;
/// Creates a test user with sensible defaults.
fn make_user(name: &str) -> User {
User::new(name, &format!("{name}@test.com")).unwrap()
}
#[test]
fn user_display() {
let user = make_user("alice");
assert_eq!(user.display_name(), "alice");
}
}Property-Based Testing with proptest
Basic Property Tests
use proptest::prelude::*;
proptest! {
#[test]
fn encode_decode_roundtrip(input in ".*") {
let encoded = encode(&input);
let decoded = decode(&encoded).unwrap();
assert_eq!(input, decoded);
}
#[test]
fn sort_preserves_length(mut vec in prop::collection::vec(any::<i32>(), 0..100)) {
let original_len = vec.len();
vec.sort();
assert_eq!(vec.len(), original_len);
}
#[test]
fn sort_produces_ordered_output(mut vec in prop::collection::vec(any::<i32>(), 0..100)) {
vec.sort();
for window in vec.windows(2) {
assert!(window[0] <= window[1]);
}
}
}Custom Strategies
use proptest::prelude::*;
fn valid_email() -> impl Strategy<Value = String> {
("[a-z]{1,10}", "[a-z]{1,5}")
.prop_map(|(user, domain)| format!("{user}@{domain}.com"))
}
proptest! {
#[test]
fn accepts_valid_emails(email in valid_email()) {
assert!(User::new("Test", &email).is_ok());
}
}Mocking with mockall
Trait-Based Mocking
use mockall::{automock, predicate::eq};
#[automock]
trait UserRepository {
fn find_by_id(&self, id: u64) -> Option<User>;
fn save(&self, user: &User) -> Result<(), StorageError>;
}
#[test]
fn service_returns_user_when_found() {
let mut mock = MockUserRepository::new();
mock.expect_find_by_id()
.with(eq(42))
.times(1)
.returning(|_| Some(User { id: 42, name: "Alice".into() }));
let service = UserService::new(Box::new(mock));
let user = service.get_user(42).unwrap();
assert_eq!(user.name, "Alice");
}
#[test]
fn service_returns_none_when_not_found() {
let mut mock = MockUserRepository::new();
mock.expect_find_by_id()
.returning(|_| None);
let service = UserService::new(Box::new(mock));
assert!(service.get_user(99).is_none());
}Doc Tests
Executable Documentation
/// Adds two numbers together.
///
/// # Examples
///
/// ```
/// use my_crate::add;
///
/// assert_eq!(add(2, 3), 5);
/// assert_eq!(add(-1, 1), 0);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
/// Parses a config string.
///
/// # Errors
///
/// Returns `Err` if the input is not valid TOML.
///
/// ```no_run
/// use my_crate::parse_config;
///
/// let config = parse_config(r#"port = 8080"#).unwrap();
/// assert_eq!(config.port, 8080);
/// ```
///
/// ```no_run
/// use my_crate::parse_config;
///
/// assert!(parse_config("}{invalid").is_err());
/// ```
pub fn parse_config(input: &str) -> Result<Config, ParseError> {
todo!()
}Benchmarking with Criterion
# Cargo.toml
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]
name = "benchmark"
harness = false// benches/benchmark.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn fibonacci(n: u64) -> u64 {
match n {
0 | 1 => n,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
fn bench_fibonacci(c: &mut Criterion) {
c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
}
criterion_group!(benches, bench_fibonacci);
criterion_main!(benches);Test Coverage
Running Coverage
# Install: cargo install cargo-llvm-cov (or use taiki-e/install-action in CI)
cargo llvm-cov # Summary
cargo llvm-cov --html # HTML report
cargo llvm-cov --lcov > lcov.info # LCOV format for CI
cargo llvm-cov --fail-under-lines 80 # Fail if below thresholdCoverage Targets
| Code Type | Target |
|---|---|
| Critical business logic | 100% |
| Public API | 90%+ |
| General code | 80%+ |
| Generated / FFI bindings | Exclude |
Testing Commands
cargo test # Run all tests
cargo test -- --nocapture # Show println output
cargo test test_name # Run tests matching pattern
cargo test --lib # Unit tests only
cargo test --test api_test # Integration tests only
cargo test --doc # Doc tests only
cargo test --no-fail-fast # Don't stop on first failure
cargo test -- --ignored # Run ignored testsBest Practices
DO:
- Write tests FIRST (TDD)
- Use
#[cfg(test)]modules for unit tests - Test behavior, not implementation
- Use descriptive test names that explain the scenario
- Prefer
assert_eq!overassert!for better error messages - Use
?in tests that returnResultfor cleaner error output - Keep tests independent — no shared mutable state
DON'T:
- Use
#[should_panic]when you can testResult::is_err()instead - Mock everything — prefer integration tests when feasible
- Ignore flaky tests — fix or quarantine them
- Use
sleep()in tests — use channels, barriers, ortokio::time::pause() - Skip error path testing
CI Integration
# GitHub Actions
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- name: Check formatting
run: cargo fmt --check
- name: Clippy
run: cargo clippy -- -D warnings
- name: Run tests
run: cargo test
- uses: taiki-e/install-action@cargo-llvm-cov
- name: Coverage
run: cargo llvm-cov --fail-under-lines 80Remember: Tests are documentation. They show how your code is meant to be used. Write them clearly and keep them up to date.
Related skills
Forks & variants (1)
Rust Testing has 1 known copy in the catalog totaling 1.3k installs. They canonicalize to this original listing.
- affaan-m - 1.3k installs
FAQ
What is the RED-GREEN-REFACTOR cycle?
RED: write a failing test first. GREEN: write minimal code to pass the test. REFACTOR: improve code while keeping tests green. Repeat for next requirement.
When should I use #[should_panic] vs Result::is_err()?
Prefer Result::is_err() for clearer error messages and better test diagnostics. Use #[should_panic] only when panic is the actual intended behavior, and always specify expected message with #[should_panic(expected = "...")].
How do I organize tests in a Rust project?
Unit tests go in #[cfg(test)] modules at the end of src files. Integration tests go in separate test binaries under tests/ directory. Use tests/common/mod.rs for shared test utilities.
Is Rust Testing safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.