Skip to main content

CliRunner test runner for command line applications.

Project description

CliRunner

A test helper for invoking and testing command line interfaces (CLIs). This is adapted from the Click CliRunner but modified to work with non-Click scripts, such as those using argparse for parsing command line arguments.

Installation

python3 -m pip install clirunner

Source Code

The source code is available on GitHub.

Motivation

I write a lot of Python command line tools. I usually reach for Click to build the CLI but sometimes will use argparse or even just manual sys.argv parsing for simple scripts or where I do not want to introduce a dependency on Click. Click provides a very useful CliRunner for testing CLIs, but it only works with Click applications. This project is a derivative of Click's CliRunner that works with non-Click scripts. The API is the same as Click's CliRunner, so it should be easy to switch between the two if you later refactor to use Click.

Basic Testing

CliRunner can invoke your CLI's main function as a command line script. The CliRunner.invoke() method runs the command line script in isolation and captures the output as both bytes and binary data.

The return value is a Result object, which has the captured output data, exit code, and optional exception attached:

hello.py

"""Simple CLI """

import argparse


def hello():
    """Print Hello World"""
    argp = argparse.ArgumentParser(description="Print Hello World")
    argp.add_argument("-n", "--name", help="Name to greet")
    args = argp.parse_args()
    print(f"Hello {args.name or 'World'}!")


if __name__ == "__main__":
    hello()

test_hello.py

"""Test hello.py"""

from hello import hello

from clirunner import CliRunner


def test_hello_world():
    runner = CliRunner()
    result = runner.invoke(hello, ["--name", "Peter"])
    assert result.exit_code == 0
    assert result.output == "Hello Peter!\n"

File System Isolation

For basic command line tools with file system operations, the CliRunner.isolated_filesystem() method is useful for setting the current working directory to a new, empty folder.

cat.py

"""Simple cat program for testing isolated file system"""

import argparse


def cat():
    argp = argparse.ArgumentParser()
    argp.add_argument("file", type=argparse.FileType("r"))
    args = argp.parse_args()
    print(args.file.read(), end="")


if __name__ == "__main__":
    cat()

test_cat.py

"""Test cat.py example."""

from cat import cat

from clirunner import CliRunner


def test_cat():
    runner = CliRunner()
    with runner.isolated_filesystem():
        with open("hello.txt", "w") as f:
            f.write("Hello World!\n")

        result = runner.invoke(cat, ["hello.txt"])
        assert result.exit_code == 0
        assert result.output == "Hello World!\n"

Pass temp_dir to control where the temporary directory is created. The directory will not be removed by CliRunner in this case. This is useful to integrate with a framework like Pytest that manages temporary files.

def test_keep_dir(tmp_path):
    runner = CliRunner()

    with runner.isolated_filesystem(temp_dir=tmp_path) as td:
        ...

Input Streams

The test wrapper can also be used to provide input data for the input stream (stdin). This is very useful for testing prompts, for instance:

prompt.py

"""Simple example for testing input streams"""


def prompt():
    foo = input("Foo: ")
    print(f"foo = {foo}")


if __name__ == "__main__":
    prompt()

test_prompt.py

"""Test prompt.py example"""

from prompt import prompt

from clirunner import CliRunner


def test_prompts():
    runner = CliRunner()
    result = runner.invoke(prompt, input="wau wau\n")
    assert not result.exception
    # note: unlike click.CliRunner, clirunner.CliRunner does not echo the input
    assert "foo = wau wau\n" in result.output

Note that the input will not be echoed to the output stream. This is different from the behavior of the input() function, which does echo the input and from click's prompt() function, which also echo's the input when under test.

Testing Click Applications

Do not use clirunner.CliRunner to test applications built with Click, Typer, or another Click derivative. Instead, use Click's built-in CliRunner. clirunner.CliRunner is only for testing non-Click scripts such as those using argparse or manual sys.argv argument parsing.

License

CliRunner is a derivative work of Click's CliRunner, and so it is licensed under the same BSD 3-clause license as Click. See the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

clirunner-0.0.1.tar.gz (26.9 kB view hashes)

Uploaded Source

Built Distribution

clirunner-0.0.1-py2.py3-none-any.whl (23.2 kB view hashes)

Uploaded Python 2 Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page