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

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

%Cheer.Test{
  return: term(),            # what run/2 returned, or {:error, :usage} on a usage error
  output: String.t(),        # everything written to stdout during the invocation
  error_output: String.t()   # cheer's diagnostics for this call
}

error_output holds what cheer wrote as diagnostics during the call: usage errors, suggestions, the help printed after an error, and deprecation warnings. They go to a device private to the call, so error_output contains nothing from other processes and exact or empty assertions on it are safe in async: true tests. Text a command's own run/2 writes to :stderr is not included; capture that with ExUnit.CaptureIO.capture_io(:stderr, ...), which sees every process's stderr and so belongs in async: false tests when you assert on it exactly.

Testing validation failures

When validation fails, cheer prints an error to stderr and does not invoke run/2. Assert on the return value and error_output:

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

  assert result.return == {:error, :usage}
  assert result.error_output =~ "must be 1-10"
  refute result.output =~ "Hello"
end

Testing subcommand routing

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

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 -- interactive equivalent of the in-process runner.