agentsclimarketplace

Rust testing

Skill rynhardt-potgieter/sprint_workflow/plugins/sprint-workflow/skills/rust-testing

Use this skill when writing any test in a Rust project — unit tests, integration tests, snapshot tests, or fixture setup. Trigger on any task involving test files, the tests/ directory, fixture projects, or cargo test commands.From its SKILL.md

Install
npx -y skills add rynhardt-potgieter/sprint_workflow --skill rust-testing

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

8.9 KB, ~2.1k tokens by cl100k_base, as published. Nobody here has run it

Rust Testing

Testing Philosophy

  • Every new command or feature gets an integration test
  • Every bug fix gets a regression test written BEFORE the fix
  • Output format is a contract — always snapshot test it
  • Never test against real user data — always use controlled fixtures
  • Test names describe the scenario, not the function

Unit Tests

Unit tests live in the same file as the code, in a #[cfg(test)] module at the bottom:

// src/core/store.rs
impl Store {
    pub fn find_item(&self, name: &str) -> Result<Option<Item>> {
        // ... production code ...
    }
}

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

    fn setup_test_store() -> (Store, tempfile::TempDir) {
        let dir = tempdir().unwrap();
        let db_path = dir.path().join("test.db");
        let store = Store::open(&db_path).unwrap();
        (store, dir)  // return dir to keep it alive
    }

    #[test]
    fn find_item_returns_none_for_unknown_name() {
        let (store, _dir) = setup_test_store();
        let result = store.find_item("NonExistent").unwrap();
        assert!(result.is_none());
    }

    #[test]
    fn find_item_finds_by_qualified_name() {
        let (mut store, _dir) = setup_test_store();

        // Insert test data
        store.upsert_item(&Item {
            id: "test::MyClass::myMethod".into(),
            name: "myMethod".into(),
            kind: ItemKind::Method,
            // ...
        }).unwrap();

        let result = store.find_item("MyClass.myMethod").unwrap();
        assert!(result.is_some());
        assert_eq!(result.unwrap().name, "myMethod");
    }

    #[test]
    fn transitive_lookup_finds_deep_dependencies() {
        let (mut store, _dir) = setup_test_store();

        // A depends on B, B depends on C — lookup from A should find C
        insert_test_chain(&mut store, vec![
            ("A", "B"),
            ("B", "C"),
        ]);

        let result = store.find_transitive("C", 3).unwrap();

        assert!(result.contains_at_depth("B", 1));
        assert!(result.contains_at_depth("A", 2));
    }
}

Unit Test Rules

  • Use tempfile::tempdir() for any test that needs filesystem state
  • Always return the TempDir handle from setup functions to keep it alive for the test's duration
  • Use unwrap() freely in tests — panics produce clear test failures
  • Name tests after the scenario: find_item_returns_none_for_unknown_name not test_find

Integration Tests

Integration tests live in tests/integration/ and test commands end-to-end against fixture projects:

// tests/integration/test_show.rs
use assert_cmd::Command;
use predicates::str::contains;

// Fixture path — small project with known structure
const FIXTURE: &str = "tests/fixtures/simple-project";

#[test]
fn show_displays_name_and_file() {
    Command::cargo_bin("mytool")
        .unwrap()
        .args(["show", "MyClass"])
        .current_dir(FIXTURE)
        .assert()
        .success()
        .stdout(contains("MyClass"))
        .stdout(contains("src/service.ts"));
}

#[test]
fn show_returns_error_for_unknown_target() {
    Command::cargo_bin("mytool")
        .unwrap()
        .args(["show", "NonExistentClass"])
        .current_dir(FIXTURE)
        .assert()
        .failure()
        .stderr(contains("not found"));
}

#[test]
fn json_output_is_valid_json() {
    let output = Command::cargo_bin("mytool")
        .unwrap()
        .args(["show", "MyClass", "--json"])
        .current_dir(FIXTURE)
        .assert()
        .success()
        .get_output()
        .stdout
        .clone();

    let json: serde_json::Value = serde_json::from_slice(&output)
        .expect("Output should be valid JSON");

    assert_eq!(json["command"], "show");
    assert!(json["data"]["name"].as_str().is_some());
}

Integration Test Rules

  • Use assert_cmd for running the binary and asserting on output
  • Use predicates for flexible stdout/stderr assertions
  • Each test should be self-contained — no shared mutable state between tests
  • Test both success and error paths

Snapshot Tests (insta)

Use insta for output format tests. Output format is a contract — changes must be explicit:

// tests/integration/test_output_format.rs
use insta::assert_snapshot;
use assert_cmd::Command;

#[test]
fn show_output_format_matches_snapshot() {
    let output = Command::cargo_bin("mytool")
        .unwrap()
        .args(["show", "MyClass"])
        .current_dir("tests/fixtures/simple-project")
        .output()
        .unwrap();

    let stdout = String::from_utf8(output.stdout).unwrap();

    // On first run: creates a snapshot file in tests/snapshots/
    // On subsequent runs: compares against saved snapshot
    assert_snapshot!("show_my_class", stdout);
}

Snapshot workflow:

# First run — creates snapshot files
cargo test

# Review new/changed snapshots
cargo insta review

# Accept all changes (be intentional about this)
cargo insta accept

Snapshot files live in tests/snapshots/ and are committed to git. Any change to output format shows up as a snapshot diff in PR review.

Test Fixtures

Fixture Design

Test fixtures are small, controlled projects with known, deterministic structure:

tests/fixtures/simple-project/
  src/
    service.ts        # A class with known methods
    types.ts          # Type definitions
    controller.ts     # Consumes the service
  config.json

The fixture has known, deterministic properties that serve as ground truth:

  • Exact counts (e.g., "MyClass has exactly 3 methods")
  • Exact relationships (e.g., "Controller calls processPayment 4 times")
  • Known structure (e.g., "Logger has exactly 2 consumers")

These numbers are the ground truth for correctness tests.

Fixture Management

If fixtures include pre-built indexes or databases, they must be committed to git and re-generated when the schema changes:

# Rebuild fixture data after schema change
cd tests/fixtures/simple-project && mytool init --full

# Commit the rebuilt data
git add tests/fixtures/
git commit -m "test(fixtures): rebuild data after schema change"

Common Test Patterns

Testing data layer queries directly

fn insert_item(store: &mut Store, name: &str, kind: ItemKind) -> String {
    let id = format!("test::{name}");
    store.upsert_item(&Item {
        id: id.clone(),
        name: name.to_string(),
        kind,
        file_path: "test.ts".to_string(),
        line_start: 1,
        line_end: 10,
        ..Default::default()
    }).unwrap();
    id
}

fn insert_edge(store: &mut Store, from: &str, to: &str, kind: EdgeKind) {
    store.upsert_edge(&Edge {
        from_id: format!("test::{from}"),
        to_id: format!("test::{to}"),
        kind,
        file_path: "test.ts".to_string(),
        line: Some(5),
    }).unwrap();
}

Testing that error messages are helpful

#[test]
fn missing_setup_gives_helpful_error() {
    let dir = tempdir().unwrap();  // empty dir, no setup

    Command::cargo_bin("mytool")
        .unwrap()
        .args(["show", "anything"])
        .current_dir(dir.path())
        .assert()
        .failure()
        .stderr(contains("Run"));  // error should suggest the fix
}

Testing incremental updates

#[test]
fn incremental_update_detects_file_change() {
    let dir = tempdir().unwrap();

    // Write initial file
    std::fs::write(dir.path().join("test.ts"), "function foo() {}").unwrap();

    // Build initial state
    Command::cargo_bin("mytool").unwrap()
        .args(["init"]).current_dir(dir.path())
        .assert().success();

    // Modify file
    std::fs::write(dir.path().join("test.ts"), "function foo() {} function bar() {}").unwrap();

    // Re-process
    Command::cargo_bin("mytool").unwrap()
        .args(["update"]).current_dir(dir.path())
        .assert().success()
        .stderr(contains("1 files changed"));

    // Verify new content is available
    Command::cargo_bin("mytool").unwrap()
        .args(["show", "bar"]).current_dir(dir.path())
        .assert().success();
}

Running Tests

# All tests
cargo test

# Specific test file
cargo test --test test_show

# Specific test by name
cargo test show_displays_name

# With output (useful for debugging)
cargo test -- --nocapture

# Snapshot review
cargo insta review

# Integration tests only
cargo test --test '*'

# Unit tests only
cargo test --lib

Key Dependencies

CratePurpose
instaSnapshot testing — output format is a contract
tempfileIsolated temp directories for filesystem tests
assert_cmdRun the binary and assert on output/exit code
predicatesFlexible assertions on stdout/stderr content
serde_jsonValidate JSON output in integration tests

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,750. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.