Skip to content

Commit 854cfb1

Browse files
authored
Rewrite testing docs and move from rst to myst (#2857)
General rewrite of testing docs: Add contents to the top. Added sub commands and context headings. Rewrote the other sections Convert to myst: Should have done this in a separate PR because the diff is basically useless. Open lastest docs side by side with docs generated by PR. The changes to the text are pretty straight forward. Added dependencies so build will succeed. Ran rst2myst to convert. Worked pretty well with a few typos. Next time should use uvx install to install in own environment since its requirements are not up to date so it will downgrade your sphinx if you install it in the project environment. Make dev environment nicer: Add sphinx-autobuild so you can run sphinx-autobuild -b html ./docs ./_build and docs changes will rebuild and update on save. I have been using it for several years and it is quite nice.
2 parents 5dd6288 + 4398887 commit 854cfb1

File tree

8 files changed

+353
-230
lines changed

8 files changed

+353
-230
lines changed

docs/api.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ API
66
This part of the documentation lists the full API reference of all public
77
classes and functions.
88

9+
.. contents::
10+
:depth: 1
11+
:local:
12+
913
Decorators
1014
----------
1115

@@ -199,6 +203,8 @@ customizing Click's shell completion system.
199203
.. autofunction:: add_completion_class
200204

201205

206+
.. _testing:
207+
202208
Testing
203209
-------
204210

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"sphinx_tabs.tabs",
1919
"sphinxcontrib.log_cabinet",
2020
"pallets_sphinx_themes",
21+
"myst_parser",
2122
]
2223
autodoc_member_order = "bysource"
2324
autodoc_typehints = "description"

docs/testing.md

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# Testing Click Applications
2+
3+
```{eval-rst}
4+
.. currentmodule:: click.testing
5+
```
6+
7+
Click provides the {ref}`click.testing <testing>` module to help you invoke command line applications and check their behavior.
8+
9+
These tools should only be used for testing since they change
10+
the entire interpreter state for simplicity. They are not thread-safe!
11+
12+
The examples use [pytest](https://docs.pytest.org/en/stable/) style tests.
13+
14+
```{contents}
15+
:depth: 1
16+
:local: true
17+
```
18+
19+
## Basic Example
20+
21+
The key pieces are:
22+
- {class}`CliRunner` - used to invoke commands as command line scripts.
23+
- {class}`Result` - returned from {meth}`CliRunner.invoke`. Captures output data, exit code, optional exception, and captures the output as bytes and binary data.
24+
25+
```{code-block} python
26+
:caption: hello.py
27+
28+
import click
29+
30+
@click.command()
31+
@click.argument('name')
32+
def hello(name):
33+
click.echo(f'Hello {name}!')
34+
```
35+
36+
```{code-block} python
37+
:caption: test_hello.py
38+
39+
from click.testing import CliRunner
40+
from hello import hello
41+
42+
def test_hello_world():
43+
runner = CliRunner()
44+
result = runner.invoke(hello, ['Peter'])
45+
assert result.exit_code == 0
46+
assert result.output == 'Hello Peter!\n'
47+
```
48+
49+
## Subcommands
50+
51+
A subcommand name must be specified in the `args` parameter {meth}`CliRunner.invoke`:
52+
53+
```{code-block} python
54+
:caption: sync.py
55+
56+
import click
57+
58+
@click.group()
59+
@click.option('--debug/--no-debug', default=False)
60+
def cli(debug):
61+
click.echo(f"Debug mode is {'on' if debug else 'off'}")
62+
63+
@cli.command()
64+
def sync():
65+
click.echo('Syncing')
66+
```
67+
68+
```{code-block} python
69+
:caption: test_sync.py
70+
71+
from click.testing import CliRunner
72+
from sync import cli
73+
74+
def test_sync():
75+
runner = CliRunner()
76+
result = runner.invoke(cli, ['--debug', 'sync'])
77+
assert result.exit_code == 0
78+
assert 'Debug mode is on' in result.output
79+
assert 'Syncing' in result.output
80+
```
81+
82+
## Context Settings
83+
84+
Additional keyword arguments passed to {meth}`CliRunner.invoke` will be used to construct the initial {class}`Context object <click.Context>`.
85+
For example, setting a fixed terminal width equal to 60:
86+
87+
```{code-block} python
88+
:caption: sync.py
89+
90+
import click
91+
92+
@click.group()
93+
def cli():
94+
pass
95+
96+
@cli.command()
97+
def sync():
98+
click.echo('Syncing')
99+
```
100+
101+
```{code-block} python
102+
:caption: test_sync.py
103+
104+
from click.testing import CliRunner
105+
from sync import cli
106+
107+
def test_sync():
108+
runner = CliRunner()
109+
result = runner.invoke(cli, ['sync'], terminal_width=60)
110+
assert result.exit_code == 0
111+
assert 'Debug mode is on' in result.output
112+
assert 'Syncing' in result.output
113+
```
114+
115+
## File System Isolation
116+
117+
The {meth}`CliRunner.isolated_filesystem` context manager sets the current working directory to a new, empty folder.
118+
119+
```{code-block} python
120+
:caption: cat.py
121+
122+
import click
123+
124+
@click.command()
125+
@click.argument('f', type=click.File())
126+
def cat(f):
127+
click.echo(f.read())
128+
```
129+
130+
```{code-block} python
131+
:caption: test_cat.py
132+
133+
from click.testing import CliRunner
134+
from cat import cat
135+
136+
def test_cat():
137+
runner = CliRunner()
138+
with runner.isolated_filesystem():
139+
with open('hello.txt', 'w') as f:
140+
f.write('Hello World!')
141+
142+
result = runner.invoke(cat, ['hello.txt'])
143+
assert result.exit_code == 0
144+
assert result.output == 'Hello World!\n'
145+
```
146+
147+
Pass in a path to control where the temporary directory is created.
148+
In this case, the directory will not be removed by Click. Its useful
149+
to integrate with a framework like Pytest that manages temporary files.
150+
151+
```{code-block} python
152+
:caption: test_cat.py
153+
154+
from click.testing import CliRunner
155+
from cat import cat
156+
157+
def test_cat_with_path_specified():
158+
runner = CliRunner()
159+
with runner.isolated_filesystem('~/test_folder'):
160+
with open('hello.txt', 'w') as f:
161+
f.write('Hello World!')
162+
163+
result = runner.invoke(cat, ['hello.txt'])
164+
assert result.exit_code == 0
165+
assert result.output == 'Hello World!\n'
166+
```
167+
168+
## Input Streams
169+
170+
The test wrapper can provide input data for the input stream (stdin). This is very useful for testing prompts.
171+
172+
```{code-block} python
173+
:caption: prompt.py
174+
175+
import click
176+
177+
@click.command()
178+
@click.option('--foo', prompt=True)
179+
def prompt(foo):
180+
click.echo(f"foo={foo}")
181+
```
182+
183+
```{code-block} python
184+
:caption: test_prompt.py
185+
186+
from click.testing import CliRunner
187+
from prompt import prompt
188+
189+
def test_prompts():
190+
runner = CliRunner()
191+
result = runner.invoke(prompt, input='wau wau\n')
192+
assert not result.exception
193+
assert result.output == 'Foo: wau wau\nfoo=wau wau\n'
194+
```
195+
196+
Prompts will be emulated so they write the input data to
197+
the output stream as well. If hidden input is expected then this
198+
does not happen.

docs/testing.rst

Lines changed: 0 additions & 158 deletions
This file was deleted.

requirements/dev.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
-r typing.txt
44
pre-commit
55
tox
6+
sphinx-autobuild

0 commit comments

Comments
 (0)