Code of the Day
IntermediateTesting CLIs

CliRunner in practice

Write pytest tests for a Click command — happy path, error path, and output content assertions — using CliRunner.

UtilitiesIntermediate8 min read
Recommended first
By the end of this lesson you will be able to:
  • Write a pytest-style test using CliRunner that asserts on exit code 0
  • Assert that output contains expected text
  • Test the error path and assert on a non-zero exit code

A test for a Click command follows the same structure as any other test: arrange (set up the command and runner), act (invoke), assert (check the result). The only difference is that the result comes from result.output and result.exit_code rather than a return value.

The command under test

Here is a small Click command to test:

import click

@click.command()
@click.argument("name")
@click.option("--loud", is_flag=True, help="Print in uppercase.")
def greet(name, loud):
    """Greet NAME."""
    if not name.strip():
        raise click.UsageError("Name cannot be blank.")
    msg = f"Hello, {name}!"
    click.echo(msg.upper() if loud else msg)

This is enough to write three meaningful tests: the happy path, the --loud flag, and an invalid argument.

Three test patterns

Happy path:

def test_greet_default():
    runner = CliRunner()
    result = runner.invoke(greet, ["Alice"])
    assert result.exit_code == 0
    assert "Hello, Alice!" in result.output

Variation (--loud flag):

def test_greet_loud():
    runner = CliRunner()
    result = runner.invoke(greet, ["Alice", "--loud"])
    assert result.exit_code == 0
    assert "HELLO, ALICE!" in result.output

Error path (missing argument):

def test_greet_missing_arg():
    runner = CliRunner()
    result = runner.invoke(greet, [])
    assert result.exit_code == 2   # Click's exit code for UsageError / missing arg
    assert "Missing argument" in result.output

Exit code 2 is Click's standard for usage errors — missing required arguments, type mismatches, and click.UsageError. Exit code 1 is the convention for runtime errors (the command ran but something went wrong). Exit code 0 means success.

Try it

Python — editable, runs in your browser

When a Click command raises an unhandled exception, result.exit_code is 1 and result.exception holds the exception object. Add assert result.exception is None to the happy-path tests so any unexpected crash causes a clear test failure rather than a silent wrong exit code.

Where to go next

Next: mocking stdin and env — using CliRunner's input= parameter for confirmation prompts and env= for environment variable injection.

Finished reading? Mark it complete to track your progress.

On this page