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.