I have a function called get_choices() - that makes an api call - that is called inside a click.Choice() decorator for a click.Command() function called cli().
I want to test the command function cli() using click's CliRunner().invoke() method but avoid the api call inside get_choices().
Below is a minimal code to use. Make sure to have click installed using pip install click.
cli.py
from click import command, option, Choice
def get_choices():
print("making an api call")
return ["name_1", "name_2"]
@command()
@option(
"--name",
prompt=True,
type=Choice(choices=get_choices()), # api call is made here
)
def cli(name):
pass # Doesn't matter what happens here
test_cli.py
from unittest.mock import patch
import click
from click.testing import CliRunner
# This avoids the api call when importing cli below
# but makes an api call itself when loading the module
patch("cli.get_choices", get_test_choices).start()
def test_cli_behaves():
runner = CliRunner()
import cli
# Replaces prod choices with test choices
# I want to avoid using params[0]
cli.cli.params[0].type = click.Choice(["name_test"])
result = runner.invoke(cli.cli, args=["--name", "name_test"])
print(result.stdout)
assert result.exit_code == 0
assert False
Is there a way to patch get_choices() before it's called inside the decorator when loading the module ?
I would like to avoid:
- Any api call
- Accessing
cli.cli.params[0]since it's not clean and becomes cumbersome when there are many options added to the cli.
I am testing by attaching a debugger to the test and adding a breakpoint on the api call in get_choices() and can confirm an api call is made on the patch line or the import line if the patch isn't used.
I've read a lot of questions/answers about patching with decorators on stackoverflow, most notably this one which I've used to inspire my current solution, but none provides what I really want.
Edit: Does the answer to this question also apply in my case ?