cli_tester
cli_tester
Crystal testing utility for CLI applications. Provides isolated environments and tools to test command-line interactions.
Features
- ๐ Shard Validation
- Automatic shard.yml detection
- Configuration parsing with YAML validation
- Required field checking (name, targets)
- Precise error locations in YAML files
 
- ๐ฅ๏ธ XDG Compliance Testing
- ๐ Environment Isolation
- ๐งน Output Normalization
- ๐ธ Snapshot Testing
- ๐ค Interactive Testing
- โก Mock Adapters
Installation
Add to shard.yml:
dependencies:
  cli_tester:
    github: dsisnero/cli_tester
Then run:
shards install
Usage
require "cli_tester"
describe "MyCLI" do
  it "tests CLI behavior" do
    CliTester.test do |env|
      # XDG Config Testing
      env.create_xdg_config("myapp", "config.yml", "debug: true")
      # Temporary ENV variables
      env.with_temp_env({"APP_MODE" => "test"}) do
        # XDG-aware command execution
        # Ensure XDG_CONFIG_HOME is set for the command
        config_home = env.env["XDG_CONFIG_HOME"]
        cmd = CliTester::Shell.xdg_command("myapp --verbose", config_home)
        result = env.execute(cmd)
        result.stdout.should contain("Debug mode enabled")
        # Example: Check cache file created by the app
        # env.read_file("#{env.env["XDG_CACHE_HOME"]}/myapp/cache.dat").should eq("cached")
      end
      # Test interactive prompts
      process = env.spawn("my_cli --interactive")
      process.wait_for_text("Enter name:")
      process.write_text("Tester")
      process.wait_for_finish
      process.stdout.should contain("Hello Tester")
    end
  end
end
Shard Validation
Test your shard.yml configuration:
it "validates shard configuration" do
  # Get parsed shard configuration
  shard = CliTester::Shard.parse(File.read(CliTester.shard_file))
  
  shard.name.should eq "my_cli"
  shard.targets["main"].main.should eq "src/main.cr"
  
  # Test invalid configurations
  invalid_yaml = <<-YAML
    version: 1.0
    targets:
      broken: {}
  YAML
  
  expect_raises(YAML::ParseException, /Missing required 'name' field/) do
    CliTester::Shard.parse(invalid_yaml)
  end
end
XDG Environment Testing
Test XDG-compliant applications without touching real configs:
it "uses XDG config locations" do
  CliTester.test do |env|
    # Create test config
    env.create_xdg_config("myapp", "settings.toml", <<-TOML
      [features]
      experimental = true
    TOML
    )
    # Verify config location
    config_path = File.join(env.env["XDG_CONFIG_HOME"], "myapp/settings.toml")
    File.exists?(config_path).should be_true
    # Test CLI behavior using XDG-aware command
    cmd = CliTester::Shell.xdg_command("myapp show-config", env.env["XDG_CONFIG_HOME"])
    result = env.execute(cmd)
    result.stdout.should contain("experimental: true")
  end
end
Environment Variable Management
Safely test environment-dependent behavior:
it "respects APP_DEBUG flag" do
  CliTester.test do |env|
    env.with_temp_env({
      "APP_DEBUG" => "1",
      "OLD_VAR"   => nil  # Unset during test
    }) do
      result = env.execute("myapp")
      result.stdout.should contain("[DEBUG]")
    end
  end
end
Output Normalization Examples
# Raw output contains color codes and paths:
"Processing \e[32m/tmp/cli-test-xyz/file.txt\e[0m"
# Normalized output becomes:
"Processing {base}/file.txt"
Snapshot Testing
Update snapshots by:
- Delete the snapshot file
- Re-run tests - new snapshot will be generated
CliTester::Snapshot.assert_match_snapshot(
  result.normalized_stdout(env),
  "spec/snapshots/main_output.txt"
)
Mock Adapters
Example mocking HTTP calls:
class MockSuccessAPI < CliTester::MockAdapter
  def apply_mocks
    MyHTTPClient.stub(:get, "https://api.example.com") do
      HTTP::Client::Response.new(200, "{\"status\":\"ok\"}")
    end
  end
end
env.with_mocks(MockSuccessAPI.new) do
  result = env.execute("my-cli fetch-data")
  # Assert against mocked response
end
Interactive Testing Flow
process = env.spawn("my-cli setup")
process.wait_for_text("Enter API key:")
process.write_text("test-key-123")
process.wait_for_text("Validating...", stream: :stderr)
process.wait_for_finish
process.get_stdout.should contain("Setup complete")
Development Notes
# Run tests with output normalization:
CRYSTAL_LOG_LEVEL=DEBUG crystal spec
# Generate test coverage:
crystal spec --error-trace --debug -Dpreview_mt
Roadmap
- Automated snapshot updates via env var
- Windows path normalization
- ANSI progress bar handling
- Multi-process concurrency testing
Contributing
- Fork it (https://github.com/dsisnero/cli_tester/fork)
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Add some feature')
- Push to the branch (git push origin my-new-feature)
- Create a new Pull Request
Contributors
- Dominic Sisneros - creator and maintainer
Repository
  cli_tester
Owner
  
  Statistic
  - 0
- 0
- 0
- 0
- 0
- 6 months ago
- April 29, 2025
License
  MIT License
Links
  
  Synced at
  Thu, 30 Oct 2025 12:18:20 GMT
Languages