references/testing-patterns.md
# RTK Testing Patterns Reference
## Untested Modules Backlog
Prioritized by testability (pure functions first, I/O-heavy last).
### High Priority (pure functions, trivial to test)
| Module | Testable Functions | Notes |
|--------|-------------------|-------|
| `diff_cmd.rs` | `compute_diff`, `similarity`, `truncate`, `condense_unified_diff` | 4 pure functions, 0 tests |
| `env_cmd.rs` | `mask_value`, `is_lang_var`, `is_cloud_var`, `is_tool_var`, `is_interesting_var` | 5 categorization functions |
### Medium Priority (need tempfile or parsed input)
| Module | Testable Functions | Notes |
|--------|-------------------|-------|
| `tracking.rs` | `estimate_tokens`, `Tracker::new`, query methods | Use tempfile for SQLite |
| `config.rs` | `Config::default`, config parsing | Test default values and TOML parsing |
| `deps.rs` | Dependency file parsing | Test with sample Cargo.toml/package.json strings |
| `summary.rs` | Output type detection heuristics | Pure string analysis |
### Low Priority (heavy I/O, CLI wiring)
| Module | Testable Functions | Notes |
|--------|-------------------|-------|
| `container.rs` | Docker/kubectl output filters | Requires mocking Command output |
| `find_cmd.rs` | Directory grouping logic | Filesystem-dependent |
| `wget_cmd.rs` | `compact_url`, `format_size`, `truncate_line`, `extract_filename_from_output` | Some pure helpers worth testing |
| `gain.rs` | Display formatting | Depends on tracking DB |
| `init.rs` | CLAUDE.md generation | File I/O |
| `main.rs` | CLI routing | Covered by smoke tests |
## RTK Test Patterns
### Pattern 1: Filter Function (most common in RTK)
```rust
#[test]
fn test_FILTER_happy_path() {
// Arrange: raw command output as string literal
let input = r#"
line of noise
line with relevant data
more noise
"#;
// Act
let result = filter_COMMAND(input);
// Assert: output contains expected, excludes noise
assert!(result.contains("relevant data"));
assert!(!result.contains("noise"));
}
```
Used in: `git.rs`, `grep_cmd.rs`, `lint_cmd.rs`, `tsc_cmd.rs`, `vitest_cmd.rs`, `pnpm_cmd.rs`, `next_cmd.rs`, `prettier_cmd.rs`, `playwright_cmd.rs`, `prisma_cmd.rs`
### Pattern 2: Pure Computation
```rust
#[test]
fn test_FUNCTION_deterministic() {
assert_eq!(truncate("hello world", 8), "hello...");
assert_eq!(truncate("short", 10), "short");
}
```
Used in: `gh_cmd.rs` (`truncate`), `utils.rs` (`truncate`, `format_tokens`, `format_usd`)
### Pattern 3: Validation / Security
```rust
#[test]
fn test_VALIDATOR_rejects_injection() {
assert!(!is_valid("malicious; rm -rf /"));
assert!(!is_valid("../../../etc/passwd"));
}
```
Used in: `pnpm_cmd.rs` (`is_valid_package_name`)
### Pattern 4: ANSI Stripping
```rust
#[test]
fn test_strip_ansi() {
let input = "\x1b[32mgreen\x1b[0m normal";
let output = strip_ansi(input);
assert_eq!(output, "green normal");
assert!(!output.contains("\x1b["));
}
```
Used in: `vitest_cmd.rs`, `utils.rs`
## Test Skeleton Template
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_FUNCTION_happy_path() {
// Arrange
let input = r#"..."#;
// Act
let result = FUNCTION(input);
// Assert
assert!(result.contains("expected"));
assert!(!result.contains("noise"));
}
#[test]
fn test_FUNCTION_empty_input() {
let result = FUNCTION("");
assert!(...);
}
#[test]
fn test_FUNCTION_edge_case() {
// Boundary conditions: very long input, special chars, unicode
}
}
```
SKILL.md
---
name: rtk-tdd
description: >
Enforces TDD (Red-Green-Refactor) for Rust development. Auto-triggers on
implementation, testing, refactoring, and bug fixing tasks. Provides
Rust-idiomatic testing patterns with anyhow/thiserror, cfg(test), and
Arrange-Act-Assert workflow.
allowed-tools:
- Read
- Write
- Edit
- Bash
effort: medium
tags: [tdd, testing, rust, red-green-refactor, rtk]
---
# Rust TDD Workflow
## Three Laws of TDD
1. Do NOT write production code without a failing test
2. Write only enough test to fail (including compilation failure)
3. Write only enough production code to pass the failing test
Cycle: **RED** (test fails) -> **GREEN** (minimum to pass) -> **REFACTOR** (cleanup, cargo test)
## Red-Green-Refactor Steps
```
1. Write test in #[cfg(test)] mod tests of the SAME file
2. cargo test MODULE::tests::test_name -- must FAIL (red)
3. Implement the minimum in the function
4. cargo test MODULE::tests::test_name -- must PASS (green)
5. Refactor if needed, re-run cargo test (still green)
6. cargo fmt && cargo clippy --all-targets && cargo test (final gate)
```
Never skip step 2. If the test passes immediately, it tests nothing.
## Idiomatic Rust Test Patterns
| Pattern | Usage | When |
|---------|-------|------|
| Arrange-Act-Assert | Base structure for every test | Always |
| `assert_eq!` / `assert!` | Direct comparison / booleans | Deterministic values |
| `assert!(result.is_err())` | Error path testing | Invalid inputs |
| `Result<()>` return type | Tests with `?` operator | Fallible functions |
| `#[should_panic]` | Expected panic | Invariants, preconditions |
| `tempfile::NamedTempFile` | File/I/O tests | Filesystem-dependent code |
## Patterns by Code Type
| Code Type | Test Pattern | Example |
|-----------|-------------|---------|
| Pure function (str -> str) | Input literal -> assert output | `assert_eq!(truncate("hello", 3), "...")` |
| Parsing/filtering | Raw string -> filter -> contains/not-contains | `assert!(filter(raw).contains("expected"))` |
| Validation/security | Boundary inputs -> assert bool | `assert!(!is_valid("../etc/passwd"))` |
| Error handling | Bad input -> `is_err()` | `assert!(parse("garbage").is_err())` |
| Struct/enum roundtrip | Construct -> serialize -> deserialize -> eq | `assert_eq!(from_str(to_str(x)), x)` |
## Naming Convention
```
test_{function}_{scenario}
test_{function}_{input_type}
```
Examples: `test_truncate_edge_case`, `test_parse_invalid_input`, `test_filter_empty_string`
## When NOT to Use Pure TDD
- Functions calling `Command::new()` -> test the parser, not the execution
- `std::process::exit()` -> refactor to `Result` first, then test the Result
- Direct I/O (SQLite, network) -> use tempfile/mock or test the pure logic separately
- Main/CLI wiring -> covered by integration/smoke tests
## Pre-Commit Gate
```bash
cargo fmt --all --check
cargo clippy --all-targets
cargo test
```
All 3 must pass. No exceptions. No `#[allow(...)]` without documented justification.