Cleo allows you to create beautiful and testable command-line interfaces.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
The testing utilities provide comprehensive tools for testing CLI commands and applications with simulated input/output, assertion helpers, and isolated test environments. These utilities enable thorough testing of command behavior without requiring real console interaction.
Test individual commands in isolation with controlled input and output.
class CommandTester:
def __init__(self, command: Command) -> None:
"""
Create a tester for a specific command.
Args:
command (Command): Command instance to test
"""
def execute(self, args: str = "", inputs: str | None = None,
interactive: bool | None = None, verbosity: Verbosity | None = None,
decorated: bool | None = None) -> int:
"""
Execute the command with simulated input.
Args:
args (str): Command arguments and options as string
inputs (str | None): Simulated user input responses
interactive (bool | None): Override interactive mode detection
verbosity (Verbosity | None): Set specific verbosity level
decorated (bool | None): Override decoration detection
Returns:
int: Command exit code
"""
@property
def command(self) -> Command:
"""Get the command being tested."""
@property
def io(self) -> IO:
"""Get the IO interface used for testing."""
def get_display(self, normalize: bool = False) -> str:
"""
Get the output display content.
Args:
normalize (bool): Whether to normalize line endings
Returns:
str: Output content from the command execution
"""
def get_error_output(self, normalize: bool = False) -> str:
"""
Get the error output content.
Args:
normalize (bool): Whether to normalize line endings
Returns:
str: Error output content from the command execution
"""
def get_status_code(self) -> int:
"""
Get the last execution status code.
Returns:
int: Exit code from last execution
"""Test complete applications with multiple commands and routing.
class ApplicationTester:
def __init__(self, application: Application) -> None:
"""
Create a tester for an application.
Args:
application (Application): Application instance to test
"""
def execute(self, args: str = "", inputs: str | None = None,
interactive: bool | None = None, verbosity: Verbosity | None = None,
decorated: bool | None = None) -> int:
"""
Execute application commands with simulated input.
Args:
args (str): Full command line including command name and arguments
inputs (str | None): Simulated user input responses
interactive (bool | None): Override interactive mode detection
verbosity (Verbosity | None): Set specific verbosity level
decorated (bool | None): Override decoration detection
Returns:
int: Application exit code
"""
@property
def application(self) -> Application:
"""Get the application being tested."""
@property
def io(self) -> IO:
"""Get the IO interface used for testing."""
def get_display(self, normalize: bool = False) -> str:
"""
Get the output display content.
Args:
normalize (bool): Whether to normalize line endings
Returns:
str: Output content from the application execution
"""
def get_error_output(self, normalize: bool = False) -> str:
"""
Get the error output content.
Args:
normalize (bool): Whether to normalize line endings
Returns:
str: Error output content from the application execution
"""
def get_status_code(self) -> int:
"""
Get the last execution status code.
Returns:
int: Exit code from last execution
"""import pytest
from cleo.testers.command_tester import CommandTester
from your_app.commands import GreetCommand
def test_greet_command_basic():
command = GreetCommand()
tester = CommandTester(command)
# Test with no arguments
exit_code = tester.execute()
assert exit_code == 0
assert "Hello" in tester.get_display()
def test_greet_command_with_name():
command = GreetCommand()
tester = CommandTester(command)
# Test with name argument
exit_code = tester.execute("John")
assert exit_code == 0
assert "Hello John" in tester.get_display()
def test_greet_command_with_yell_option():
command = GreetCommand()
tester = CommandTester(command)
# Test with --yell flag
exit_code = tester.execute("John --yell")
assert exit_code == 0
assert "HELLO JOHN" in tester.get_display()def test_status_command_output():
command = StatusCommand()
tester = CommandTester(command)
exit_code = tester.execute("--verbose")
output = tester.get_display()
# Test formatted output
assert exit_code == 0
assert "System Status:" in output
assert "Service 1: Running" in output
assert "Service 2: Stopped" in output
# Test verbose output only appears with flag
tester.execute("")
normal_output = tester.get_display()
assert len(output) > len(normal_output)
def test_error_output():
command = ProcessCommand()
tester = CommandTester(command)
# Test error conditions
exit_code = tester.execute("nonexistent-file.txt")
assert exit_code == 1
error_output = tester.get_error_output()
assert "File not found" in error_outputdef test_interactive_confirmation():
command = DeleteCommand()
tester = CommandTester(command)
# Test with "yes" input
exit_code = tester.execute("important-file.txt", inputs="yes\n")
assert exit_code == 0
assert "File deleted" in tester.get_display()
# Test with "no" input
exit_code = tester.execute("important-file.txt", inputs="no\n")
assert exit_code == 1
assert "Operation cancelled" in tester.get_display()
def test_interactive_questions():
command = ConfigCommand()
tester = CommandTester(command)
# Simulate multiple user inputs
inputs = "myproject\n8080\nyes\nproduction\n"
exit_code = tester.execute("", inputs=inputs)
assert exit_code == 0
output = tester.get_display()
assert "Project: myproject" in output
assert "Port: 8080" in output
assert "Environment: production" in output
def test_choice_questions():
command = SetupCommand()
tester = CommandTester(command)
# Test single choice
exit_code = tester.execute("", inputs="2\n") # Select second option
assert exit_code == 0
# Test invalid choice handling
exit_code = tester.execute("", inputs="invalid\n1\n") # Invalid then valid
assert exit_code == 0
assert "Invalid choice" in tester.get_display()def test_argument_validation():
command = ProcessCommand()
tester = CommandTester(command)
# Test missing required argument
exit_code = tester.execute("")
assert exit_code == 1
assert "Not enough arguments" in tester.get_error_output()
# Test valid arguments
exit_code = tester.execute("input.txt output.txt")
assert exit_code == 0
def test_option_parsing():
command = BuildCommand()
tester = CommandTester(command)
# Test short options
exit_code = tester.execute("-v -o dist/")
assert exit_code == 0
# Test long options
exit_code = tester.execute("--verbose --output-dir=dist/")
assert exit_code == 0
# Test option values
tester.execute("--format json")
assert command.option("format") == "json"def test_application_command_routing():
from your_app.application import create_application
app = create_application()
tester = ApplicationTester(app)
# Test help command
exit_code = tester.execute("help")
assert exit_code == 0
assert "Available commands:" in tester.get_display()
# Test list command
exit_code = tester.execute("list")
assert exit_code == 0
output = tester.get_display()
assert "process" in output # Our custom command should be listed
assert "config" in output
def test_application_command_execution():
app = create_application()
tester = ApplicationTester(app)
# Test executing specific commands
exit_code = tester.execute("process input.txt --format json")
assert exit_code == 0
exit_code = tester.execute("config --interactive", inputs="yes\nproduction\n")
assert exit_code == 0
def test_command_not_found():
app = create_application()
tester = ApplicationTester(app)
exit_code = tester.execute("nonexistent-command")
assert exit_code == 1
assert "Command not found" in tester.get_error_output()def test_verbosity_levels():
command = DiagnosticCommand()
tester = CommandTester(command)
# Test quiet mode
exit_code = tester.execute("--quiet")
quiet_output = tester.get_display()
assert exit_code == 0
assert len(quiet_output.strip()) == 0 # No output in quiet mode
# Test normal verbosity
exit_code = tester.execute("")
normal_output = tester.get_display()
assert "Basic info" in normal_output
assert "Debug info" not in normal_output
# Test verbose mode
exit_code = tester.execute("--verbose")
verbose_output = tester.get_display()
assert "Basic info" in verbose_output
assert "Detailed info" in verbose_output
assert "Debug info" not in verbose_output
# Test debug mode
exit_code = tester.execute("-vvv")
debug_output = tester.get_display()
assert "Basic info" in debug_output
assert "Detailed info" in debug_output
assert "Debug info" in debug_outputdef test_table_output():
command = ReportCommand()
tester = CommandTester(command)
exit_code = tester.execute("")
output = tester.get_display()
# Check table structure
assert "Name" in output # Header
assert "Status" in output # Header
assert "Service A" in output # Data row
assert "Running" in output # Data row
# Check table formatting (borders, alignment)
lines = output.split('\n')
header_line = next(line for line in lines if "Name" in line)
assert "│" in header_line or "|" in header_line # Table borders
def test_progress_output():
command = ProcessCommand()
tester = CommandTester(command)
exit_code = tester.execute("--show-progress")
output = tester.get_display()
# Progress bars create temporary output that gets cleared
# Test for completion message instead
assert "Processing complete" in output
assert exit_code == 0import pytest
import tempfile
import os
@pytest.fixture
def temp_dir():
"""Create a temporary directory for test files."""
with tempfile.TemporaryDirectory() as tmpdir:
yield tmpdir
@pytest.fixture
def sample_config_file(temp_dir):
"""Create a sample configuration file."""
config_path = os.path.join(temp_dir, "config.json")
with open(config_path, 'w') as f:
f.write('{"database": {"host": "localhost", "port": 5432}}')
return config_path
def test_command_with_files(sample_config_file):
command = LoadConfigCommand()
tester = CommandTester(command)
exit_code = tester.execute(f"--config {sample_config_file}")
assert exit_code == 0
assert "Configuration loaded" in tester.get_display()
class TestCommandHelper:
"""Helper class for common test patterns."""
@staticmethod
def execute_command(command_class, args="", inputs=None):
"""Execute a command and return tester for assertions."""
command = command_class()
tester = CommandTester(command)
exit_code = tester.execute(args, inputs)
return tester, exit_code
@staticmethod
def assert_success(tester, exit_code, expected_output=None):
"""Assert command executed successfully."""
assert exit_code == 0
if expected_output:
assert expected_output in tester.get_display()
@staticmethod
def assert_error(tester, exit_code, expected_error=None):
"""Assert command failed with error."""
assert exit_code != 0
if expected_error:
assert expected_error in tester.get_error_output()
# Usage of helper
def test_with_helper():
tester, exit_code = TestCommandHelper.execute_command(
GreetCommand,
"John --yell"
)
TestCommandHelper.assert_success(tester, exit_code, "HELLO JOHN")@pytest.mark.parametrize("name,expected", [
("John", "Hello John"),
("Alice", "Hello Alice"),
("", "Hello"),
])
def test_greet_variations(name, expected):
command = GreetCommand()
tester = CommandTester(command)
exit_code = tester.execute(name)
assert exit_code == 0
assert expected in tester.get_display()
@pytest.mark.parametrize("args,should_succeed", [
("valid-file.txt", True),
("", False), # Missing required argument
("nonexistent.txt", False), # File doesn't exist
])
def test_file_processing(args, should_succeed):
command = ProcessFileCommand()
tester = CommandTester(command)
exit_code = tester.execute(args)
if should_succeed:
assert exit_code == 0
assert "Success" in tester.get_display()
else:
assert exit_code != 0Install with Tessl CLI
npx tessl i tessl/pypi-cleo