Skip to content

Workflow: Testing ​

Updated Mar 2026

Overview ​

Rust has first-class testing support built into the language and Cargo. This workflow covers unit tests, integration tests, doc tests, property-based testing, and CI integration.

Testing Strategy ​

Unit Tests ​

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

fn internal_helper(x: i32) -> i32 {
    x * 2
}

#[cfg(test)]  // Only compiled when running tests
mod tests {
    use super::*;  // Import parent module

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

    #[test]
    fn test_add_negative() {
        assert_eq!(add(-1, 1), 0);
    }

    // Can test private functions!
    #[test]
    fn test_internal_helper() {
        assert_eq!(internal_helper(5), 10);
    }

    #[test]
    #[should_panic(expected = "overflow")]
    fn test_overflow() {
        add(i32::MAX, 1);  // Should panic
    }

    #[test]
    fn test_result() -> Result<(), String> {
        let result = add(2, 2);
        if result == 4 {
            Ok(())
        } else {
            Err(format!("Expected 4, got {result}"))
        }
    }

    #[test]
    #[ignore]  // Skip unless explicitly requested
    fn expensive_test() {
        // Long-running test
        std::thread::sleep(std::time::Duration::from_secs(10));
    }
}

Integration Tests ​

my_project/
├── src/
│   └── lib.rs
└── tests/
    ├── common/
    │   └── mod.rs      # Shared test helpers
    ├── api_tests.rs     # Each file is a separate test crate
    └── db_tests.rs
rust
// tests/common/mod.rs
pub fn setup() -> TestContext {
    // Shared setup logic
    TestContext::new()
}

// tests/api_tests.rs
mod common;

use my_project::*;

#[test]
fn test_full_workflow() {
    let ctx = common::setup();
    let result = process_data("input");
    assert!(result.is_ok());
    assert_eq!(result.unwrap().len(), 5);
}

Doc Tests ​

rust
/// Adds two numbers together.
///
/// # Examples
///
/// ```
/// use my_crate::add;
///
/// let result = add(2, 3);
/// assert_eq!(result, 5);
/// ```
///
/// Negative numbers work too:
///
/// ```
/// use my_crate::add;
/// assert_eq!(add(-1, 1), 0);
/// ```
///
/// # Panics
///
/// This example shows what NOT to do:
///
/// ```rust,should_panic
/// // This will panic on overflow in debug mode
/// my_crate::add(i32::MAX, 1);
/// ```
///
/// Code that shouldn't be run:
///
/// ```rust,no_run
/// // This connects to a real server
/// my_crate::connect("production.example.com");
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

Test Organization Patterns ​

Running Tests ​

bash
# Run all tests
cargo test

# Run specific test by name
cargo test test_add

# Run tests in specific module
cargo test tests::test_add

# Run specific integration test file
cargo test --test api_tests

# Run only ignored tests
cargo test -- --ignored

# Run all tests including ignored
cargo test -- --include-ignored

# Show println! output (normally captured)
cargo test -- --nocapture

# Run tests serially (default is parallel)
cargo test -- --test-threads=1

# Run only doc tests
cargo test --doc

# Run tests in specific package (workspace)
cargo test -p my-core

# Run tests with specific features
cargo test --features "serde"

Assertion Macros ​

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

    #[test]
    fn assertion_examples() {
        // Basic assertions
        assert!(true);
        assert_eq!(1 + 1, 2);
        assert_ne!(1, 2);

        // With custom messages
        let x = 5;
        assert!(x > 0, "x should be positive, got {x}");
        assert_eq!(x, 5, "x should be 5 but was {x}");

        // Float comparison (no assert_eq for floats!)
        let f = 0.1 + 0.2;
        assert!((f - 0.3).abs() < f64::EPSILON);
    }
}

Test Fixtures and Setup ​

rust
struct TestDb {
    connection: Connection,
}

impl TestDb {
    fn new() -> Self {
        let conn = Connection::open_in_memory().unwrap();
        conn.execute("CREATE TABLE users (id INTEGER, name TEXT)", []).unwrap();
        TestDb { connection: conn }
    }
}

impl Drop for TestDb {
    fn drop(&mut self) {
        // Cleanup happens automatically
    }
}

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

    #[test]
    fn test_with_db() {
        let db = TestDb::new();  // Setup
        db.connection.execute("INSERT INTO users VALUES (1, 'Alice')", []).unwrap();

        let count: i32 = db.connection
            .query_row("SELECT COUNT(*) FROM users", [], |row| row.get(0))
            .unwrap();
        assert_eq!(count, 1);
    }  // TestDb::drop() cleans up
}

Property-Based Testing ​

rust
// Cargo.toml: proptest = "1"

use proptest::prelude::*;

fn reverse(s: &str) -> String {
    s.chars().rev().collect()
}

proptest! {
    #[test]
    fn reverse_twice_is_identity(s in "\\PC*") {
        assert_eq!(reverse(&reverse(&s)), s);
    }

    #[test]
    fn reverse_preserves_length(s in "\\PC*") {
        assert_eq!(reverse(&s).len(), s.len());
    }

    #[test]
    fn add_is_commutative(a in -1000i32..1000, b in -1000i32..1000) {
        assert_eq!(add(a, b), add(b, a));
    }
}

CI Integration ​

yaml
# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy, rustfmt
      - uses: Swatinem/rust-cache@v2

      - name: Check formatting
        run: cargo fmt --check

      - name: Clippy
        run: cargo clippy -- -D warnings

      - name: Build
        run: cargo build --verbose

      - name: Test
        run: cargo test --verbose

      - name: Test (all features)
        run: cargo test --all-features

Checklist ​

  • [ ] Unit tests for all core logic (in same file with #[cfg(test)])
  • [ ] Integration tests for public API (in tests/ directory)
  • [ ] Doc tests for all public functions with examples
  • [ ] Edge cases: empty input, max values, error paths
  • [ ] cargo test passes with no failures
  • [ ] cargo clippy passes with no warnings
  • [ ] CI pipeline runs tests on every push

Built with VitePress | Rust SOP Documentation