docs/guides/testing.md

# Testing

Cheer ships an in-process test runner that captures output and returns the
handler's return value. No subprocess, no argv escaping, no port juggling.

## Basic

```elixir
test "greet prints a greeting" do
  result = Cheer.Test.run(MyApp.CLI.Greet, ["world"])

  assert result.return == :ok
  assert result.output =~ "Hello, world!"
end
```

`Cheer.Test.run/3` takes the same arguments as `Cheer.run/3`: a command
module, argv, and an optional `prog`.

## Return shape

```elixir
%Cheer.Test{
  return: term(),       # whatever your run/2 returned (or :ok if cheer handled an error)
  output: String.t()    # everything written to stdout during the invocation
}
```

## Testing validation failures

When validation fails, cheer prints an error and does not invoke `run/2`.
Assert on the captured output:

```elixir
test "rejects out-of-range times" do
  result = Cheer.Test.run(MyApp.CLI.Greet, ["world", "--times", "100"])

  assert result.output =~ "must be 1-10"
  refute result.output =~ "Hello"
end
```

## Testing subcommand routing

```elixir
test "db migrate with a target" do
  result = Cheer.Test.run(Devtool.CLI, ["db", "migrate", "--target", "20240101"])

  assert result.output =~ "Migrating to version 20240101"
end
```

Same as production: the router walks the tree, parses each level's options,
and dispatches to the leaf's `run/2`.

## Testing help output

```elixir
test "help includes all subcommands" do
  result = Cheer.Test.run(Devtool.CLI, ["--help"])

  assert result.output =~ "server"
  assert result.output =~ "db"
end
```

## Integration tests

For end-to-end tests that exercise the real escript binary, use the
usual `System.cmd/2` against your built artifact. Cheer's test runner
is for fast, in-process unit tests of parsing and handler logic, not
for testing the escript launcher itself.

## See also

- [REPL mode](repl.md) -- interactive equivalent of the in-process
  runner.