---
name: rust-testing-strategy
description: HASH Rust testing strategy. Use when writing Rust unit, integration, or snapshot tests, or choosing assertion and test-organization patterns.
license: AGPL-3.0
metadata:
  triggers:
    type: domain
    enforcement: suggest
    priority: high
    keywords:
      - rust test
      - cargo nextest
      - insta
      - similar_asserts
    intent-patterns:
      - "\\b(write|add|run|fix)\\b.*?\\b(rust|cargo)\\b.*?\\btest"
      - "\\b(nextest|snapshot test|insta)\\b"
---

# Rust Testing Strategy

## Verification Commands

- When modifying Rust code, run clippy to detect issues:

  ```bash
  cargo clippy --all-features --all-targets --workspace --no-deps
  ```

- Use `cargo doc --no-deps --all-features` to check documentation

## Test Execution

- Use `cargo-nextest` for running unit and integration tests
- Use the default test runner for documentation tests
- For comprehensive verification, run the complete test suite
- Database seeding can be performed using yarn commands in the project's package.json

## Test Subject

Test behavior implemented by our code or its composition. Assume dependencies implement their documented behavior correctly. Do not add tests whose only subject is a dependency's parser, serializer or generated implementation. Deriving or wrapping that API on an application type does not make its implementation ours.

A test that only calls `try_parse_from` on a `#[derive(clap::Parser)]` type and checks the declared fields or built-in validation re-tests clap. Test the logic of an application-owned value parser, validation rule or use of the parsed options when the case can expose a bug in our behavior with clap's behavior held fixed. Parsing can be setup for the test without being its whole assertion. Moving a dependency-conformance case into an integration target does not change its subject.

## Test Design Principles

- Choose success and error cases that distinguish our intended behavior from a plausible defect
- Do not add a test merely to mirror a trivial mapping or immediate rejection branch
- Test boundary conditions and edge cases explicitly
- Include tests for invalid or malformed inputs
- For streaming encoders/decoders, test partial data handling and buffer management
- Aim for high test coverage but prioritize test quality over quantity
- Structure tests following the Arrange-Act-Assert pattern

## Assertion Standards

- Use descriptive assertion messages that explain the expected behavior
- All assertion messages (including `expect()`, `unwrap()` and `assert*()`) should follow the "should..." format:

  ```rust
  // Good examples:
  value.expect("should contain a valid configuration");
  assert_eq!(result, expected, "Result should match the expected value");
  ```

- Use `expect()` or `expect_err()` with clear messages instead of `unwrap()` or `unwrap_err()`
- Prefer `assert_eq!` with custom messages over bare assertions when comparing values
- When testing errors, assert on specific error types or message contents, not just that an error occurred
- Balance assertions to verify functionality without creating brittle tests

## Test Organization

- Group related tests into appropriate modules
- Use helper functions to avoid code duplication in tests
- Consider using parameterized tests for testing similar functionality with different inputs

## Test Naming

A test name is a short label with the shape `<subject>_<case>[_<variant>]`:

- `<subject>` is the operation, type or function under test. It does not repeat the name of the enclosing module, because the test path already carries that.
- `<case>` is what distinguishes this test from its siblings under the same subject: an operand shape, an input class, a code path, or an active verb and its object where the behaviour is the distinguishing part (`load_redacts_mistyped_values`). An active verb is not narration; `should`, `works` and `correctly` are.
- `<variant>` narrows the case further when two tests share one, and is otherwise absent.

Tests of one subject share a prefix, so a module's test list reads as a table of subject × case and `cargo nextest run -E 'test(<subject>_)'` selects the family. The name identifies the case and the assertions establish its result. A comment adds setup, reasoning or a contract that those do not explain. A self-explanatory test needs no comment.

What a name never carries:

- A `test_` prefix, an article, a narration verb (`should`, `works`, `correctly`) or a `when`/`with`/`that` clause. Put necessary explanation in a comment or assertion message rather than in the name.
- More than about six words. Three to five is the norm, and a longer name is a test doing too much, where the fix is one test per case.
- The rejection, for a negative case. The name is the input class (`ice_invalid_subscript_type`, `rank_positions_short`, `rows_out_of_domain`), with no `err_` prefix or `_err` suffix. An outcome word is the final token only when the case alone is ambiguous (`eq_same_type_accepted`).

Where the shape meets a test framework:

- A property test, or an `rstest` case set, takes its name from the property it tests (`lattice_laws`, `solve_anti_symmetry`, `condensation_is_dag`).
- Renaming a snapshot test renames the snapshot files derived from its name in the same change.

```rust
// Before: a sentence, three cases in one test, the outcome in the name.
async fn rank_pair_tampers_name_their_own_variants() { /* … */ }
async fn short_node_identity_table_refuses() { /* … */ }

// After: one label per case, the outcome in the doc comment.
/// Open refuses a reverse rank permutation short of the code column, under `Columns`.
async fn rank_positions_short() { /* … */ }
/// Open refuses a reverse rank permutation that is no permutation, under `RankInverse`.
async fn rank_positions_constant() { /* … */ }
/// Open refuses a node identity table short of the code column, under `Identities`.
async fn node_identities_short() { /* … */ }
```

## Test Code Quality

- Follow the same code quality standards in test code as in production code
- Add appropriate assertions for array/slice access to avoid clippy warnings
- Add comments for non-obvious setup, reasoning or contracts that the test's name and body leave unclear. Self-explanatory tests may omit comments.
- Consider adding custom test utilities to simplify common testing patterns
- Use the `json!` macro from `serde_json` instead of constructing JSON as raw strings

```rust
// Bad:
let json_str = "{\"name\":\"value\",\"nested\":{\"key\":42}}";

// Good:
use serde_json::json;
let json_value = json!({
    "name": "value",
    "nested": {
        "key": 42
    }
});
```
