Code of the Day
IntermediateTesting CLIs

Mocking stdin and env

Simulate user input and environment variables in CLI tests using CliRunner's input= and env= parameters.

UtilitiesIntermediate5 min read
By the end of this lesson you will be able to:
  • 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.output

For 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 == 0

These 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 == 0

monkeypatch.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.

Finished reading? Mark it complete to track your progress.

On this page