# Test CLI input and output with `CaptureIO`

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.

```elixir
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](https://ex-unit.hexdocs.pm/1.18.3/ExUnit.CaptureIO.html#capture_io/2)


---

Created by: almirsarajcic
Date: July 31, 2026
URL: https://elixirdrops.net/d/nmQw4hUr
