How to Mock CLI Output in Typer with unittest.mock

Mock and capture Typer CLI output using unittest.mock.patch and io.StringIO for testing command-line applications.

Medium Python 3.9+ Aug 9, 2026 Modern tooling 11 views 0 copies

Requires third-party packages — install first
pip install typer

Python code

25 lines
Python 3.9+
import typer
from unittest.mock import patch
import io

app = typer.Typer()

@app.command()
def greet(name: str, age: int = 18, uppercase: bool = False):
    """Greet a person with optional formatting."""
    message = f"Hello {name}, age {age}"
    if uppercase:
        message = message.upper()
    typer.echo(message)

def main():
    # Capture CLI output using mock
    test_io = io.StringIO()
    with patch("typer.echo", side_effect=test_io.write):
        with typer.testing.CliRunner() as runner:
            result = runner.invoke(app, ["greet", "Alice", "--age", "30", "--uppercase"])
            print(f"Exit code: {result.exit_code}")
            print(f"Output: {result.output.strip()}")

if __name__ == "__main__":
    main()

Output

stdout
Exit code: 0
Output: HELLO ALICE, AGE 30

How it works

The code defines a Typer app with a greet command that takes name, age, and a boolean flag for uppercase output. Using patch("typer.echo", side_effect=test_io.write) replaces Typer's output function with a custom writer that captures text into an in-memory StringIO buffer. The CliRunner from typer.testing provides an isolated test environment for invoking the command with arguments. This approach allows you to inspect result.output and directly test the output without needing a real terminal or subprocess. The side_effect parameter is key — it lets the wrapper call the real StringIO write method instead of returning a substitute value.

Common mistakes

  • Forgetting to use `io.StringIO()` before passing it to `side_effect` as `test_io.write`
  • Using `patch("typer.echo")` without arguments when you need to capture the output
  • Not importing `typer.testing.CliRunner` in the same file as the test code
  • Confusing `result.output` (captured stdout) with `sys.stdout` after the runner context exits

Variations

  1. Use `capsys` fixture from pytest for simpler output capture without explicit patching
  2. Call `runner.invoke()` with `input=...` instead of passing arguments when testing interactive prompts

Real-world use cases

  • Writing unit tests for CLI tools where you need to assert exact command output matches expected text.
  • Creating golden-file tests where captured CLI output is compared against stored snapshots in CI.
  • Building automated documentation examples that execute a CLI command and embed its real output in generated docs.

Sponsored

Run locally

This sample needs third-party packages, so it cannot run in the browser IDE. Copy the code above, install the packages shown at the top, then run it in your own Python environment.

More from Modern tooling

Related tutorials and quizzes for this topic.