Skip to content
agentic-jumpstart-testing logo

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

  1. Always set JARVY_TEST_MODE=1 to prevent interactive prompts
  2. Use JARVY_FAST_TEST when not testing actual installation
  3. Keep unit tests fast by mocking external commands
  4. Use platform-specific modules with #[cfg(target_os)]
  5. Test error paths - verify exit codes and messages
  6. Use tempfile for isolation - temporary config files
  7. Mark slow tests with #[ignore]
  8. Test both config formats - simple and detailed