Skip to content

Commit

Permalink
✅ Add tests for typer command (Typer CLI)
Browse files Browse the repository at this point in the history
  • Loading branch information
tiangolo committed Mar 30, 2024
1 parent 443b2a0 commit a07a81e
Show file tree
Hide file tree
Showing 26 changed files with 1,056 additions and 0 deletions.
Empty file added tests/assets/cli/__init__.py
Empty file.
8 changes: 8 additions & 0 deletions tests/assets/cli/app_other_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import typer

application = typer.Typer()


@application.command()
def callback(name: str = "World"):
typer.echo(f"Hello {name}")
Empty file.
2 changes: 2 additions & 0 deletions tests/assets/cli/func_other_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def some_function(name: str = "World"):
print(f"Hello {name}")
40 changes: 40 additions & 0 deletions tests/assets/cli/multi_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import typer

sub_app = typer.Typer()

variable = "Some text"


@sub_app.command()
def hello(name: str = "World", age: int = typer.Option(0, help="The age of the user")):
"""
Say Hello
"""
typer.echo(f"Hello {name}")


@sub_app.command()
def hi(user: str = typer.Argument("World", help="The name of the user to greet")):
"""
Say Hi
"""


@sub_app.command()
def bye():
"""
Say bye
"""
typer.echo("sub bye")


app = typer.Typer(help="Demo App", epilog="The end")
app.add_typer(sub_app, name="sub")


@app.command()
def top():
"""
Top command
"""
typer.echo("top")
22 changes: 22 additions & 0 deletions tests/assets/cli/multi_app_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import typer

sub_app = typer.Typer()


@sub_app.command()
def hello():
typer.echo("sub hello")


@sub_app.command()
def bye():
typer.echo("sub bye")


cli = typer.Typer()
cli.add_typer(sub_app, name="sub")


@cli.command()
def top():
typer.echo("top")
12 changes: 12 additions & 0 deletions tests/assets/cli/multi_func.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
message = "Stuff"


def say_stuff():
print(message)


def main(name: str = "World"):
"""
Say hi to someone, by default to the World.
"""
print(f"Hello {name}")
102 changes: 102 additions & 0 deletions tests/assets/cli/multiapp-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# `multiapp`

Demo App

**Usage**:

```console
$ multiapp [OPTIONS] COMMAND [ARGS]...
```

**Options**:

* `--install-completion`: Install completion for the current shell.
* `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
* `--help`: Show this message and exit.

The end

**Commands**:

* `sub`
* `top`: Top command

## `multiapp sub`

**Usage**:

```console
$ multiapp sub [OPTIONS] COMMAND [ARGS]...
```

**Options**:

* `--help`: Show this message and exit.

**Commands**:

* `bye`: Say bye
* `hello`: Say Hello
* `hi`: Say Hi

### `multiapp sub bye`

Say bye

**Usage**:

```console
$ multiapp sub bye [OPTIONS]
```

**Options**:

* `--help`: Show this message and exit.

### `multiapp sub hello`

Say Hello

**Usage**:

```console
$ multiapp sub hello [OPTIONS]
```

**Options**:

* `--name TEXT`: [default: World]
* `--age INTEGER`: The age of the user [default: 0]
* `--help`: Show this message and exit.

### `multiapp sub hi`

Say Hi

**Usage**:

```console
$ multiapp sub hi [OPTIONS] [USER]
```

**Arguments**:

* `[USER]`: The name of the user to greet [default: World]

**Options**:

* `--help`: Show this message and exit.

## `multiapp top`

Top command

**Usage**:

```console
$ multiapp top [OPTIONS]
```

**Options**:

* `--help`: Show this message and exit.
1 change: 1 addition & 0 deletions tests/assets/cli/not_python.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is not Python
25 changes: 25 additions & 0 deletions tests/assets/cli/sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import typer

app = typer.Typer()


@app.command()
def hello(name: str = "World", formal: bool = False):
"""
Say hi
"""
if formal:
typer.echo(f"Good morning Ms. {name}")
else:
typer.echo(f"Hello {name}!")


@app.command()
def bye(friend: bool = False):
"""
Say bye
"""
if friend:
typer.echo("Goodbye my friend")
else:
typer.echo("Goodbye")
Empty file added tests/test_cli/__init__.py
Empty file.
41 changes: 41 additions & 0 deletions tests/test_cli/test_app_other_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import subprocess
import sys


def test_script_help():
result = subprocess.run(
[
sys.executable,
"-m",
"coverage",
"run",
"-m",
"typer",
"tests/assets/cli/app_other_name.py",
"run",
"--help",
],
capture_output=True,
encoding="utf-8",
)
assert "--name" in result.stdout


def test_script():
result = subprocess.run(
[
sys.executable,
"-m",
"coverage",
"run",
"-m",
"typer",
"tests/assets/cli/app_other_name.py",
"run",
"--name",
"Camila",
],
capture_output=True,
encoding="utf-8",
)
assert "Hello Camila" in result.stdout
20 changes: 20 additions & 0 deletions tests/test_cli/test_completion_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import os
import subprocess
import sys


def test_script_completion_run():
result = subprocess.run(
[sys.executable, "-m", "coverage", "run", "-m", "typer"],
capture_output=True,
encoding="utf-8",
env={
**os.environ,
"___MAIN__.PY_COMPLETE": "complete_bash",
"_PYTHON _M TYPER_COMPLETE": "complete_bash",
"COMP_WORDS": "typer tests/assets/cli/sample.py",
"COMP_CWORD": "2",
"_TYPER_COMPLETE_TESTING": "True",
},
)
assert "run" in result.stdout
112 changes: 112 additions & 0 deletions tests/test_cli/test_doc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import subprocess
import sys
from pathlib import Path


def test_doc():
result = subprocess.run(
[
sys.executable,
"-m",
"coverage",
"run",
"-m",
"typer",
"tests.assets.cli.multi_app",
"utils",
"docs",
"--name",
"multiapp",
],
capture_output=True,
encoding="utf-8",
)
docs_path: Path = Path(__file__).parent.parent / "assets/cli/multiapp-docs.md"
docs = docs_path.read_text()
assert docs in result.stdout
assert "**Arguments**" in result.stdout


def test_doc_output(tmp_path: Path):
out_file: Path = tmp_path / "out.md"
result = subprocess.run(
[
sys.executable,
"-m",
"coverage",
"run",
"-m",
"typer",
"tests.assets.cli.multi_app",
"utils",
"docs",
"--name",
"multiapp",
"--output",
str(out_file),
],
capture_output=True,
encoding="utf-8",
)
docs_path: Path = Path(__file__).parent.parent / "assets/cli/multiapp-docs.md"
docs = docs_path.read_text()
written_docs = out_file.read_text()
assert docs in written_docs
assert "Docs saved to:" in result.stdout


def test_doc_not_existing():
result = subprocess.run(
[
sys.executable,
"-m",
"coverage",
"run",
"-m",
"typer",
"no_typer",
"utils",
"docs",
],
capture_output=True,
encoding="utf-8",
)
assert "Could not import as Python module:" in result.stderr


def test_doc_no_typer():
result = subprocess.run(
[
sys.executable,
"-m",
"coverage",
"run",
"-m",
"typer",
"tests/assets/cli/empty_script.py",
"utils",
"docs",
],
capture_output=True,
encoding="utf-8",
)
assert "No Typer app found" in result.stderr


def test_doc_file_not_existing():
result = subprocess.run(
[
sys.executable,
"-m",
"coverage",
"run",
"-m",
"typer",
"assets/cli/not_existing.py",
"utils",
"docs",
],
capture_output=True,
encoding="utf-8",
)
assert "Not a valid file or Python module:" in result.stderr
Loading

0 comments on commit a07a81e

Please sign in to comment.