agentic-jumpstart-testing
Testing guidelines for Jarvy CLI - unit testing patterns, integration tests with assert_cmd, test environment variables, platform-specific testing, and CI coverage strategies.
SKILL.md
Full skill instructions
Testing Guidelines
This skill provides testing patterns and strategies for the Jarvy CLI project.
Test Organization
Unit Tests Location
Unit tests live alongside code:
// src/tools/git/git.rs
pub fn ensure(min_hint: &str) -> Result<(), InstallError> {
// Implementation
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ensure_parses_version() {
// Test implementation
}
}
Integration Tests Location
tests/
├── cli_dispatch.rs # Command routing
├── cli_config_errors.rs # Config error handling
├── tools_install.rs # Tool installation
└── ...
Environment Variables
JARVY_TEST_MODE=1
Disables interactive prompts:
#[test]
fn test_setup() {
Command::cargo_bin("jarvy")
.unwrap()
.env("JARVY_TEST_MODE", "1")
.arg("setup")
.assert()
.success();
}
JARVY_FAST_TEST
Skips external command execution:
#[test]
fn test_config_parsing() {
Command::cargo_bin("jarvy")
.unwrap()
.env("JARVY_FAST_TEST", "1")
.env("JARVY_TEST_MODE", "1")
.arg("setup")
.assert()
.success();
}
JARVY_RUN_EXTERNAL_CMDS_IN_TEST
Enables external commands in unit tests (use sparingly):
#[test]
#[ignore]
fn test_actual_installation() {
std::env::set_var("JARVY_RUN_EXTERNAL_CMDS_IN_TEST", "1");
// Test that calls real commands
}
Test Utilities
make_config() Helper
use tempfile::TempDir;
use std::fs;
fn make_config(content: &str) -> TempDir {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("jarvy.toml");
fs::write(&config_path, content).unwrap();
temp_dir
}
#[test]
fn test_simple_config() {
let temp = make_config(r#"
[provisioner]
git = "2.40"
"#);
Command::cargo_bin("jarvy")
.unwrap()
.env("JARVY_TEST_MODE", "1")
.env("JARVY_FAST_TEST", "1")
.arg("setup")
.current_dir(temp.path())
.assert()
.success();
}
Integration Testing with assert_cmd
Basic CLI Invocation
use assert_cmd::Command;
use predicates::prelude::*;
#[test]
fn test_help() {
Command::cargo_bin("jarvy")
.unwrap()
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("Usage:"));
}
Testing Exit Codes
#[test]
fn test_config_error() {
let temp = make_config("invalid toml {{{{");
Command::cargo_bin("jarvy")
.unwrap()
.env("JARVY_TEST_MODE", "1")
.arg("setup")
.current_dir(temp.path())
.assert()
.code(2); // CONFIG_ERROR
}
Predicate Assertions
use predicates::str::{contains, is_empty};
#[test]
fn test_output() {
Command::cargo_bin("jarvy")
.unwrap()
.env("JARVY_TEST_MODE", "1")
.arg("get")
.assert()
.success()
.stdout(contains("tools"));
}
Platform-Specific Testing
Conditional Test Compilation
#[test]
#[cfg(target_os = "macos")]
fn test_homebrew() {
// macOS-specific test
}
#[test]
#[cfg(target_os = "linux")]
fn test_apt() {
// Linux-specific test
}
#[test]
#[cfg(target_os = "windows")]
fn test_winget() {
// Windows-specific test
}
Platform Test Modules
#[cfg(test)]
mod tests {
#[cfg(target_os = "macos")]
mod macos_tests {
#[test]
fn test_brew_install() { }
}
#[cfg(target_os = "linux")]
mod linux_tests {
#[test]
fn test_apt_install() { }
}
}
Unit Testing Patterns
Testing Tool Handlers
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_parsing() {
assert!(version_satisfies("2.40.0", "2.40"));
assert!(!version_satisfies("2.39.0", "2.40"));
}
}
Testing Config Parsing
#[test]
fn test_simple_format() {
let config = r#"
[provisioner]
git = "2.40"
"#;
let parsed: Config = toml::from_str(config).unwrap();
assert_eq!(parsed.tools.get("git").unwrap().version, "2.40");
}
#[test]
fn test_detailed_format() {
let config = r#"
[provisioner]
node = { version = "20.0", version_manager = true }
"#;
let parsed: Config = toml::from_str(config).unwrap();
let node = parsed.tools.get("node").unwrap();
assert!(node.version_manager);
}
CI Configuration
GitHub Actions
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo test --verbose -- --show-output
env:
JARVY_TEST_MODE: "1"
JARVY_FAST_TEST: "1"
Coverage
cargo install cargo-llvm-cov
cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info
Test Categories
// Fast tests (default)
#[test]
fn test_config_parsing() { }
// Slow tests (run with --ignored)
#[test]
#[ignore]
fn test_actual_installation() { }
// Platform-specific
#[test]
#[cfg(target_os = "macos")]
fn test_homebrew() { }
Running Tests
# All tests
cargo test --verbose -- --show-output
# Single test file
cargo test --test cli_dispatch -- --show-output
# Tests matching pattern
cargo test config -- --show-output
# Ignored (slow) tests
cargo test -- --ignored
# All including ignored
cargo test -- --include-ignored
Best Practices
- Always set JARVY_TEST_MODE=1 to prevent interactive prompts
- Use JARVY_FAST_TEST when not testing actual installation
- Keep unit tests fast by mocking external commands
- Use platform-specific modules with
#[cfg(target_os)] - Test error paths - verify exit codes and messages
- Use tempfile for isolation - temporary config files
- Mark slow tests with #[ignore]
- Test both config formats - simple and detailed
