Skip to content

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

Closed

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'
    

Activity

  1. changed the title [-]`testing.CliRunner.invoke` cannot pass `color` for Context instanciation[/-] [+]`testing.CliRunner.invoke` cannot pass `color` for Context instantiation[/+] on Oct 26, 2021
  2. davidism commented on Oct 26, 2021

    @davidism
    Member

    The color argument and attribute is annotated as color: Optional[bool], so passing other values is not supported.

  3. kdeldycke commented on Oct 26, 2021

    @kdeldycke
    CollaboratorAuthor

    The color argument and attribute is annotated as color: Optional[bool], so passing other values is not supported.

    Ahah yes indeed! 😅 My example was not that good. The reality is that all I want to pass is a boolean, i.e. end up with either:

    • my_cli.main(color=True)
    • my_cli.main(color=False)

    Anyway, I just came up with a convoluted way to bypass that limitation by redefining the invoke() command and monkeypatching on the fly the command's main(). It kind of crappy but does the job.

    All in all, should we consider this as a testing.CliRunner.invoke bug? By the way, how do you test that case in Click itself?

  4. davidism commented on Oct 26, 2021

    @davidism
    Member

    You can modify command.context_settings["color"] to change it for the command.

    def test_color(monkeypatch, runner):
        monkeypatch.setitem(cli.context_settings, "color", True)
        runner.invoke(cli, color=False)

    I'd have to see where it's tested in Click, the color argument was added in version 4 before I started maintaining it. The invoke arg sets whether the "terminal" supports color, and the Context arg sets whether to override that. It's not a bug though, but I don't think it's worth going through a deprecation cycle to rename right now. It seems like a fairly niche case to want to both set the terminal support and force the color code output anyway, and can be handled with context_settings.

    Passing invoke(color=True) essentially does what's expected, tells Click to output color by default. Either you don't want to test the color codes in the output, so you don't pass the arg, or you do want the codes and pass the arg. Also overriding the context seems redundant.

  5. locked as resolved and limited conversation to collaborators on Nov 10, 2021
  6. added a commit that references this issue on Oct 26, 2026
    e3a1b8d
  7. added this to the 8.5.1 milestone on Oct 26, 2026
  8. added 2 commits that reference this issue on Oct 26, 2026
    6140c6b
    ee07b7c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    test runnerCliRunner and Click's test utilities feature

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions