Mocking stdin and env
Simulate user input and environment variables in CLI tests using CliRunner's input= and env= parameters.
- Use CliRunner's input= parameter to simulate stdin for confirmation prompts
- Use CliRunner's env= dict to inject environment variables during a test
- Explain when to use pytest's monkeypatch.setenv() instead
Real CLI tools interact with the world beyond their arguments: they prompt for confirmation, they read API keys from environment variables, they behave differently in CI than on a developer's machine. Good tests cover all of these paths, and CliRunner provides clean hooks for each one.
Simulating stdin with input=
click.confirm() and click.prompt() read from stdin. In a test you do not
want to block waiting for keyboard input. Pass input= to runner.invoke()
with the string your test should type:
runner = CliRunner()
result = runner.invoke(delete_cmd, ["data.csv"], input="y\n")CliRunner feeds that string as stdin. The command reads "y\n" and proceeds
as if the user typed y and pressed Enter.
To simulate the user saying no:
result = runner.invoke(delete_cmd, ["data.csv"], input="n\n")
assert result.exit_code == 0
assert "Aborted" in result.outputFor multi-step prompts, include all responses separated by newlines:
# Prompts: "Confirm deletion? [y/N]" then "Type the filename to confirm: "
result = runner.invoke(delete_cmd, [], input="y\ndata.csv\n")Injecting environment variables with env=
Pass a dict to the env= parameter to inject environment variables for the
duration of the invocation:
result = runner.invoke(
api_cmd,
["fetch"],
env={"MYTOOL_API_KEY": "test-key-abc"}
)
assert result.exit_code == 0These variables are set only for that invocation and cleaned up automatically. They do not affect other tests or the host environment.
When to use monkeypatch.setenv()
CliRunner's env= only injects into Click's option resolution mechanism when
auto_envvar_prefix is set. If your command reads os.environ directly — for
example, key = os.environ.get("API_KEY") — that call bypasses Click's env
handling. Use pytest's monkeypatch.setenv() for those cases:
def test_with_env_key(monkeypatch):
monkeypatch.setenv("API_KEY", "test-key")
runner = CliRunner()
result = runner.invoke(api_cmd, ["fetch"])
assert result.exit_code == 0monkeypatch.setenv() patches os.environ for the duration of the test and
restores it automatically. It is the correct tool whenever the code under test
reads environment variables without going through Click's option layer.
Prefer auto_envvar_prefix over direct os.environ access in Click
commands. It keeps env var names consistent with option names, makes the
behaviour self-documenting in --help, and makes env= testing work cleanly
without monkeypatching.
Where to go next
Next: snapshot testing — golden-file comparison to assert that your tool's output format does not change unexpectedly.