Test the business logic directly
Every command is just a function, so unit tests can bypass the CLI entirely:build_demo_cli() or your custom builder. This keeps failure messages focused on your own logic, not argument parsing.
Invoke commands via CLI.run
When you need to exercise parsing, use the public API instead of subprocess:
- Pass
argvto avoid mutatingsys.argv. - Capture console output by monkeypatching
cli.ui.console.printor by injecting a Rich console backed byio.StringIO.
Call CLI.invoke for targeted scenarios
CLI.invoke(["cmd", ...]) expects the command tokens (without program name). This is useful when you already parsed or constructed arguments elsewhere and only want to execute the decorated function.
Validate parsing errors
CLI.parse_for_invoke exposes the same validation logic used by the shell. You can assert that incorrect input produces helpful errors:
Testing the shell loop
Shell integrates with prompt_toolkit, which is hard to run in headless tests. Instead of simulating keystrokes, focus on:
cli.parse_for_invokefor validation.Shell._build_completer()if you extend the class and want to ensure your nested dictionary is correct.- Helper modules such as
builtins.AliasStorefor alias expansion.
CLI.run(argv=...) gives you confidence without spinning up a subprocess or an interactive session.