Skip to content

testing.CliRunner.invoke cannot pass color for Context instantiation #2110

@kdeldycke

Description

@kdeldycke

TL;DR

click.testing.CliRunner.invoke and click.Context signatures shares a common color parameter.

So when the former call the later via click.BaseCommand.main, that command's main() cannot get a color parameter in its **extra (because it has been consumed by click.testing.CliRunner.invoke).

Description

I'm in the middle of developing a set of extensions for Click under the name click-extra. It involves a lot of colouring helpers. In my testing suite I need to simulate a command that will end up having its default context's color property initialized to user's liking.

For that I need the **extra dictionnary of click.testing.CliRunner.invoke to contain a "color": "my_custom_value" item. The problem is that invoke() method redefine itself the color property. Which means its signature consume my default value and is never appended to the **extra dictionary of the original command's main() call.

Current behavior

The current implementation allows for the invoke command to specify the color rendering of the output:

import click
from click.testing import CliRunner


runner = CliRunner()


@click.command()
@click.pass_context
def my_cli(ctx):
    click.echo(click.style("It works!", fg="blue"))
    click.echo(f"Context.color = {ctx.color!r}")


def test_vanilla_invokation():

    # Default invokation strip colors, no context influence.
    result = runner.invoke(my_cli)
    assert result.exit_code == 0
    assert "It works!" in result.output
    assert "Context.color = None" in result.output

    # Force results to strip colors, no context influence.
    result = runner.invoke(my_cli, color=False)
    assert result.exit_code == 0
    assert "It works!" in result.output
    assert "Context.color = None" in result.output

    # Force results to keep colors, no context influence.
    result = runner.invoke(my_cli, color=True)
    assert result.exit_code == 0
    assert "\x1b[34mIt works!\x1b[0m" in result.output
    assert "Context.color = None" in result.output

Notice how it does not have any influence on the command's context.

Expected behavior

I'm looking to have an influence on the command context. Here is what I expect invoke() would allow me to do:

def test_context_color_initialization():

    extra = {"color": "my_custom_value"}

    # Default invokation strip colors, no context influence.
    result = runner.invoke(my_cli, **extra)
    assert result.exit_code == 0
    assert "It works!" in result.output
    assert "Context.color = None" in result.output

    # Force results to strip colors, no context influence.
    result = runner.invoke(my_cli, color=False, **extra)
    assert result.exit_code == 0
    assert "It works!" in result.output
    assert "Context.color = 'my_custom_value'" in result.output

    # Force results to keep colors, no context influence.
    result = runner.invoke(my_cli, color=True, **extra)
    assert result.exit_code == 0
    assert "\x1b[34mIt works!\x1b[0m" in result.output
    assert "Context.color = 'my_custom_value'" in result.output

Of course it doesn't work as invoke() ends up with 2 values for color, and is thus unable to pass one of them in the form of my_cli.main(color="my_custom_value") for context instanciation.

Environment

  • Python version:

    $ python
    Python 3.9.7 (default, Oct 13 2021, 06:45:31) 
    [Clang 13.0.0 (clang-1300.0.29.3)] on darwin
    
  • Click version:

    >>> import click
    >>> click.__version__
    '8.0.3'
    

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions