rust-testing

v2026.09.24

Rust testing with cargo test, tokio-test, and mockall. Covers unit tests, integration tests, async testing, mocking, and benchmarks. USE WHEN: user mentions "rust test", "cargo test", "mockall", asks about "#[test]", "#[tokio::test]", "proptest", "criterion", "async rust testing" DO NOT USE FOR: JavaScript/TypeScript - use `vitest` or `jest`; Java - use `junit`; Python - use `pytest`; Go - use `go-testing`; E2E browser tests - use Playwright

GitHub
安装命令
npx skhub add claude-dev-suite/rust-testing
Markdown
SKILL.md

Rust Testing Core Knowledge

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: rust for comprehensive documentation.

Full Reference: See advanced.md for HTTP Testing with wiremock, Property-Based Testing with proptest, Benchmarks with criterion, and Test Coverage with cargo-tarpaulin.

When NOT to Use This Skill

  • JavaScript/TypeScript Projects - Use vitest or jest
  • Java Projects - Use junit for Java testing
  • Python Projects - Use pytest for Python
  • Go Projects - Use go-testing skill
  • E2E Browser Testing - Use Playwright or Selenium

Basic Testing

Unit Tests

// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

pub fn divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None
    } else {
        Some(a / b)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }

    #[test]
    fn test_divide() {
        assert_eq!(divide(10.0, 2.0), Some(5.0));
    }

    #[test]
    fn test_divide_by_zero() {
        assert_eq!(divide(10.0, 0.0), None);
    }
}

Running Tests

# Run all tests
cargo test

# Run specific test
cargo test test_add

# Run tests in specific module
cargo test tests::

# Run tests with output
cargo test -- --nocapture

# Run tests sequentially
cargo test -- --test-threads=1

# Run ignored tests
cargo test -- --ignored

Assertions

#[cfg(test)]
mod tests {
    #[test]
    fn test_assertions() {
        // Equality
        assert_eq!(2 + 2, 4);
        assert_ne!(2 + 2, 5);

        // Boolean
        assert!(true);
        assert!(!false);

        // Custom message
        assert!(1 + 1 == 2, "Math is broken!");
        assert_eq!(2 + 2, 4, "Expected {} but got {}", 4, 2 + 2);
    }

    #[test]
    fn test_floating_point() {
        let result = 0.1 + 0.2;
        let expected = 0.3;

        // Approximate comparison for floats
        assert!((result - expected).abs() < 1e-10);
    }
}

Expected Panics

pub fn divide_or_panic(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("Cannot divide by zero!");
    }
    a / b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic]
    fn test_panic() {
        divide_or_panic(10, 0);
    }

    #[test]
    #[should_panic(expected = "Cannot divide by zero")]
    fn test_panic_message() {
        divide_or_panic(10, 0);
    }
}

Result-Based Tests

#[cfg(test)]
mod tests {
    #[test]
    fn test_with_result() -> Result<(), String> {
        if 2 + 2 == 4 {
            Ok(())
        } else {
            Err("Math failed".to_string())
        }
    }
}

Ignored Tests

#[test]
#[ignore]
fn expensive_test() {
    // Long running test
    std::thread::sleep(std::time::Duration::from_secs(60));
}

#[test]
#[ignore = "requires database connection"]
fn test_database() {
    // Test that requires external resources
}

Integration Tests

// tests/integration_test.rs
use my_crate::{add, divide};

#[test]
fn test_add_integration() {
    assert_eq!(add(100, 200), 300);
}

// tests/common/mod.rs - Shared test utilities
pub fn setup() {
    // Setup code
}

// tests/another_test.rs
mod common;

#[test]
fn test_with_setup() {
    common::setup();
    // Test code
}

Async Testing

Tokio Test

# Cargo.toml
[dev-dependencies]
tokio = { version = "1", features = ["full", "test-util"] }
use tokio::time::{sleep, Duration};

async fn async_add(a: i32, b: i32) -> i32 {
    sleep(Duration::from_millis(10)).await;
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_async_add() {
        let result = async_add(2, 3).await;
        assert_eq!(result, 5);
    }

    #[tokio::test]
    async fn test_multiple_async() {
        let (a, b) = tokio::join!(
            async_add(1, 2),
            async_add(3, 4)
        );
        assert_eq!(a, 3);
        assert_eq!(b, 7);
    }
}

Testing with Time

use tokio::time::{self, Duration, Instant};

async fn delayed_operation() -> &'static str {
    time::sleep(Duration::from_secs(10)).await;
    "done"
}

#[cfg(test)]
mod tests {
    use super::*;
    use tokio::time::{pause, advance};

    #[tokio::test]
    async fn test_with_time_control() {
        pause(); // Pause time

        let start = Instant::now();
        let future = delayed_operation();

        // Advance time instantly
        advance(Duration::from_secs(10)).await;

        let result = future.await;
        assert_eq!(result, "done");

        // Very little real time has passed
        assert!(start.elapsed() < Duration::from_secs(1));
    }
}

Mocking with mockall

# Cargo.toml
[dev-dependencies]
mockall = "0.12"

Basic Mocking

use mockall::{automock, predicate::*};

#[automock]
trait Database {
    fn get(&self, key: &str) -> Option<String>;
    fn set(&mut self, key: &str, value: &str) -> bool;
}

struct Service<D: Database> {
    db: D,
}

impl<D: Database> Service<D> {
    fn new(db: D) -> Self {
        Self { db }
    }

    fn get_value(&self, key: &str) -> String {
        self.db.get(key).unwrap_or_else(|| "default".to_string())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_get_value_exists() {
        let mut mock = MockDatabase::new();
        mock.expect_get()
            .with(eq("key1"))
            .times(1)
            .returning(|_| Some("value1".to_string()));

        let service = Service::new(mock);
        assert_eq!(service.get_value("key1"), "value1");
    }

    #[test]
    fn test_get_value_missing() {
        let mut mock = MockDatabase::new();
        mock.expect_get()
            .with(eq("missing"))
            .times(1)
            .returning(|_| None);

        let service = Service::new(mock);
        assert_eq!(service.get_value("missing"), "default");
    }
}

Mock Expectations

#[cfg(test)]
mod tests {
    use super::*;
    use mockall::Sequence;

    #[test]
    fn test_call_count() {
        let mut mock = MockDatabase::new();
        mock.expect_get()
            .times(3)  // Exactly 3 times
            .returning(|_| Some("value".to_string()));

        let service = Service::new(mock);
        service.get_value("a");
        service.get_value("b");
        service.get_value("c");
    }

    #[test]
    fn test_call_sequence() {
        let mut seq = Sequence::new();
        let mut mock = MockDatabase::new();

        mock.expect_get()
            .with(eq("first"))
            .times(1)
            .in_sequence(&mut seq)
            .returning(|_| Some("1".to_string()));

        mock.expect_get()
            .with(eq("second"))
            .times(1)
            .in_sequence(&mut seq)
            .returning(|_| Some("2".to_string()));

        let service = Service::new(mock);
        assert_eq!(service.get_value("first"), "1");
        assert_eq!(service.get_value("second"), "2");
    }
}

Async Mock

use mockall::{automock, predicate::*};
use async_trait::async_trait;

#[async_trait]
#[automock]
trait AsyncDatabase {
    async fn get(&self, key: &str) -> Option<String>;
    async fn set(&self, key: &str, value: &str) -> bool;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_async_mock() {
        let mut mock = MockAsyncDatabase::new();
        mock.expect_get()
            .with(eq("key"))
            .times(1)
            .returning(|_| Some("value".to_string()));

        let result = mock.get("key").await;
        assert_eq!(result, Some("value".to_string()));
    }
}

Checklist

  • Unit tests for all public functions
  • Integration tests for module interactions
  • Async tests with tokio-test
  • Mock external dependencies
  • Property-based tests for algorithms
  • Benchmarks for performance-critical code
  • Coverage reporting
  • CI integration

Anti-Patterns

Anti-PatternWhy It's BadSolution
Testing private functionsCoupled to implementationTest through public API
Not using #[should_panic]Missing error validationTest expected panics explicitly
Shared mutable state in testsFlaky testsUse test isolation
Not using Result<()> in testsCan't use ? operatorReturn Result<(), Error>
Ignoring async testsWrong runtime behaviorUse #[tokio::test] for async
Not benchmarkingPerformance regressionsUse Criterion for benchmarks
Missing property testsEdge cases missedUse proptest for algorithms

Quick Troubleshooting

ProblemLikely CauseSolution
"test result: FAILED. 0 passed"Panic in testCheck panic message, add proper assertions
Async test timeoutMissing await or infinite loopEnsure all futures are awaited
Mock expectation failedWrong method callsVerify mockall expectations
"cannot find derive macro"Missing dev-dependencyAdd mockall to [dev-dependencies]
Benchmark not runningWrong harness settingSet harness = false in [[bench]]
Coverage tool crashesIncompatible versionUse cargo-tarpaulin or llvm-cov

Reference Documentation

发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

2026年9月24日

分类

未分类

许可证

MIT

源路径

skills/testing/rust-testing

默认分支

main

最新提交

9496306

Tree SHA

fe4e2f1