Last modified: Aug 12, 2025 By Alexander Williams

Python Typer CLI Testing with CliRunner

Testing CLI applications is crucial for reliability. Python Typer and CliRunner make it easy. This guide covers how to test Typer apps effectively.

What is CliRunner?

CliRunner is a utility from Click for testing CLI apps. It works perfectly with Typer since Typer builds on Click. Use it to simulate command execution.

The runner captures output and exit codes. This lets you verify behavior without manual testing. It's ideal for automated testing pipelines.

Setting Up CliRunner

First, install Typer and pytest if needed:


pip install typer pytest

Import CliRunner in your test file:


from typer.testing import CliRunner

Create a runner instance at module level:


runner = CliRunner()

Basic Command Testing

Test a simple Typer app with invoke():


import typer

app = typer.Typer()

@app.command()
def hello(name: str):
    typer.echo(f"Hello {name}")

def test_hello():
    result = runner.invoke(app, ["--name", "World"])
    assert result.exit_code == 0
    assert "Hello World" in result.output

The test checks both exit code and output. Always verify exit codes for proper error handling.

Testing Exit Codes

CLI apps use exit codes to signal success or failure. Test them with CliRunner:


@app.command()
def fail():
    typer.echo("Error!", err=True)
    raise typer.Exit(code=1)

def test_fail():
    result = runner.invoke(app, ["fail"])
    assert result.exit_code == 1
    assert "Error!" in result.stderr

For more on exit codes, see our Python Typer Exit Codes Guide.

Testing User Input

Test interactive prompts with input parameter:


@app.command()
def greet():
    name = typer.prompt("Your name?")
    typer.echo(f"Hello {name}")

def test_greet():
    result = runner.invoke(app, ["greet"], input="Alice\n")
    assert "Hello Alice" in result.output

Testing Error Conditions

Verify your app handles errors properly:


@app.command()
def divide(x: int, y: int):
    try:
        typer.echo(x / y)
    except ZeroDivisionError:
        typer.echo("Cannot divide by zero!", err=True)
        raise typer.Exit(1)

def test_divide():
    # Test success case
    result = runner.invoke(app, ["divide", "10", "2"])
    assert result.exit_code == 0
    assert "5.0" in result.output
    
    # Test error case
    result = runner.invoke(app, ["divide", "10", "0"])
    assert result.exit_code == 1
    assert "Cannot divide by zero" in result.stderr

For advanced error handling, check our Python Typer Error Handling Guide.

Testing Subcommands

Typer supports subcommands for modular CLIs. Test them like main commands:


app = typer.Typer()
admin = typer.Typer()
app.add_typer(admin, name="admin")

@admin.command()
def resetdb():
    typer.echo("Database reset!")

def test_subcommand():
    result = runner.invoke(app, ["admin", "resetdb"])
    assert result.exit_code == 0
    assert "Database reset" in result.output

Learn more in our Subcommands Guide.

Best Practices

Follow these tips for effective CLI testing:

  • Test both happy and error paths
  • Verify exit codes and output
  • Keep tests isolated and independent
  • Test edge cases and invalid inputs

Conclusion

CliRunner makes Typer CLI testing straightforward. Test commands, subcommands, and error handling reliably. Combine it with pytest for a powerful testing setup.

Remember to test exit codes, output, and error conditions. This ensures your CLI works as expected in all scenarios.