Test CLI input and output with `CaptureIO`

almirsarajcic

almirsarajcic

2 hours ago

0 comments

Test terminal behavior through the real IO calls instead of replacing IO.gets/1 or IO.write/2 with mocks. ExUnit.CaptureIO.capture_io/2 supplies stdin, captures stdout, and restores the test process’s IO device afterward.

defmodule MyApp.CLITest do
  use ExUnit.Case, async: true

  import ExUnit.CaptureIO

  test "confirms account deletion" do
    output =
      capture_io("delete\n", fn ->
        assert MyApp.CLI.confirm_deletion() == :confirmed
      end)

    assert output == "Type delete to confirm: "
  end
end

The first argument becomes the input returned by calls such as IO.gets/1 inside the callback. The function returns everything written to the captured standard IO device, so one test can verify both the decision and the exact prompt a user sees.

This works by replacing the current process’s group leader for the duration of the callback. Code that uses the standard IO device follows that group leader automatically, which makes the test exercise the same calls as production code.

CaptureIO is process-oriented: output from unrelated processes is not automatically part of the capture. If the command starts a task, pass the capture device to that process with capture_io(device_pid, fun) or keep the assertion at the synchronous CLI boundary.

Use ExUnit.CaptureLog for Logger messages. Logger handlers are separate from the standard IO device, so console output and application logs need different test helpers.

ExUnit.CaptureIO.capture_io/2 docs

Comments (0)

Sign in with GitHub to join the discussion