6

I have a command-line script with Python-click with an argument and option:

# console.py
import click

@click.command()
@click.version_option()
@click.argument("filepath", type=click.Path(exists=True), default=".")
@click.option(
    "-m",
    "--max-size",
    type=int,
    help="Max size in megabytes.",
    default=20,
    show_default=True,
)
def main(filepath: str, max_size: int) -> None:
    max_size_bytes = max_size * 1024 * 1024  # convert to MB
    if filepath.endswith(".pdf"):
        print("success")
    else:
        print(max_size_bytes)

Both the argument and option have default values and work on the command-line and using the CLI it behaves as expected. But when I try testing it following Click documentation and debug it, it does not enter the first line:

# test_console.py
from unittest.mock import Mock

import click.testing
import pytest
from pytest_mock import MockFixture

from pdf_split_tool import console

@pytest.fixture
def runner() -> click.testing.CliRunner:
    """Fixture for invoking command-line interfaces."""
    return click.testing.CliRunner()

@pytest.fixture
def mock_pdf_splitter_pdfsplitter(mocker: MockFixture) -> Mock:
    """Fixture for mocking pdf_splitter.PdfSplitter."""
    return mocker.patch("pdf_split_tool.pdf_splitter.PdfSplitter", autospec=True)

def test_main_uses_specified_filepath(
    runner: click.testing.CliRunner,
    mock_pdf_splitter_pdfsplitter: Mock, 
) -> None:
    """It uses the specified filepath."""
    result = runner.invoke(console.main, ["test.pdf"])
    assert result.exit_code == 0

I couldn't see why it is giving since the debugger did not enter the first line of function main(). Any ideas of what could be wrong?

1
  • 1
    I've been using click's testing package for a while, but it has caused me bugs and grief and I switched to invoking my entry points myself. You might want to do the same Commented Jun 4, 2020 at 21:34

2 Answers 2

7
+50

The failure is due to following error.

(pdb)print result.output
"Usage: main [OPTIONS] [FILEPATH]\nTry 'main --help' for help.\n\nError: Invalid value for '[FILEPATH]': Path 'test.pdf' does not exist.\n"

This is happening due to following code in console.py which checks if the filepath exists. @click.argument("filepath", type=click.Path(exists=True), default=".")

One way to test creating a temporary file is using afterburner's code:

# test_console.py
def test_main_uses_specified_filepath() -> None:
    runner = click.testing.CliRunner()
    with runner.isolated_filesystem():
        with open('test.pdf', 'w') as f:
            f.write('Hello World!')

        result = runner.invoke(main, ["test.pdf"])
    assert result.exit_code == 0
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks apoorva kamath! How can I see this error? afterburner just posted a test code as a complement to your answer. If you just could complete your answer with the code (since the bounty specified it needed code) I will mark as the right answer.
5

I've changed your test method to the following. However, this is more an augmentation to apoorva kamath's answer.


def test_main_uses_specified_filepath() -> None:
    runner = click.testing.CliRunner()
    with runner.isolated_filesystem():
        with open('test.pdf', 'w') as f:
            f.write('Hello World!')

        result = runner.invoke(main, ["test.pdf"])
    assert result.exit_code == 0

Simply put, it creates an isolated file system that gets cleaned up after the text is executed. So any files created there are destroyed with it.

For more information, Click's Isolated Filesystem documentation might come in handy.

Alternatively, you can remove the exists=True parameter to your file path.

4 Comments

Thanks @afterburner. I think the answer is a merge of both your answers. Since apoorva kamath answered first I will give the right answer to her (and an upvote for you).
@staticdev appreciate it, and I approve, she's the one that figured it out
Just a minor improvement, as f and the f.write are not necessary to work. You can put the invoke in the place of f.write instead. =p
True. I wasn't sure if Python created an empty file if nothing was written to it, but today I learnt :D

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.