You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This project uses [poetry][] to manage dependencies and builds, and you will need to install it before working on Decoy.
6
+
7
+
Once poetry is installed, you should be good to set up a virtual environment and install development dependencies. While Decoy is supported on Python >= 3.7, Python >= 3.8 is recommended for development.
8
+
9
+
```bash
10
+
git clone https://github.com/mcous/decoy.git
11
+
cd decoy
12
+
poetry install
13
+
```
14
+
15
+
## Development Tasks
16
+
17
+
### Tests
18
+
19
+
Decoy's tests are run using [pytest][].
20
+
21
+
```bash
22
+
poetry run pytest
23
+
```
24
+
25
+
You can also run tests in watch mode using [pytest-xdist][].
26
+
27
+
```bash
28
+
poetry run pytest --looponfail
29
+
```
30
+
31
+
In an exciting twist, since version 1.6.0, Decoy's tests rely on Decoy itself to test (and more importantly, design) the relationships between Decoy's internal APIs. This means:
32
+
33
+
- Decoy's unit test suite serves as an end-to-end test of Decoy by virtue of existing (wow, very meta, actually kind of cool).
34
+
- Changes that break a small part of Decoy may result in a large number of test failures, because if Decoy breaks it can't be used to test itself.
35
+
36
+
If you find yourself in a situation where Decoy's test suite has blown up, **concentrate on getting the test suites that don't use Decoy to pass**. From there, lean on the type-checker to guide you to any components that aren't properly hooked up. Decoy also has a end-to-end smoke test suite (`tests/test_decoy.py`) that can be helpful in getting things back to green.
37
+
38
+
### Checks
39
+
40
+
Decoy's source code is typechecked with [mypy][] and linted with [flake8][].
41
+
42
+
```bash
43
+
poetry run mypy
44
+
poetry run flake8
45
+
```
46
+
47
+
### Formatting
48
+
49
+
Decoy's source code is formatted using [black][].
50
+
51
+
```bash
52
+
poetry run black .
53
+
```
54
+
55
+
### Documentation
56
+
57
+
Decoy's documentation is built with [mkdocs][], which you can use to preview the documentation site locally.
58
+
59
+
```bash
60
+
poetry run mkdocs serve
61
+
```
62
+
63
+
## Deploying
64
+
65
+
The library and documentation will be deployed to PyPI and GitHub Pages, respectively, by CI. To trigger the deploy, cut a new version and push it to GitHub.
66
+
67
+
Deploy adheres to [semantic versioning][], so care should be taken to bump accurately.
68
+
69
+
```bash
70
+
# checkout the main branch and pull down latest changes
71
+
git checkout main
72
+
git pull
73
+
74
+
# bump the version
75
+
# replace ${bump_version} with a bump specifier, like "minor"
76
+
poetry version ${bump_version}
77
+
78
+
# add the bumped pyproject.toml
79
+
git add pyproject.toml
80
+
81
+
# commit and tag the bump
82
+
# replace ${release_version} with the actual version string
The `decoy` fixture is function-scoped and will ensure that all stub and spy state is reset between every test.
57
54
58
-
[pytest]: https://docs.pytest.org/
59
-
60
55
### Mypy Setup
61
56
62
57
Decoy's API can be a bit confusing to [mypy][]. To suppress mypy errors that may be emitted during valid usage of the Decoy API, we have a mypy plugin that you should add to your configuration file:
63
58
64
59
```ini
65
-
#mypi.ini
60
+
#mypy.ini
66
61
67
62
# ...
68
63
plugins = decoy.mypy
69
64
# ...
70
65
```
71
66
67
+
## Basic Usage
68
+
69
+
This example assumes you are using [pytest][]. See Decoy's [documentation][] for a more detailed usage guide and API reference.
70
+
71
+
### Define your test
72
+
73
+
Decoy will add a `decoy` fixture that provides its mock creation API.
74
+
75
+
```python
76
+
from decoy import Decoy
77
+
from todo import TodoAPI, TodoItem
78
+
from todo.store TodoStore
79
+
80
+
deftest_add_todo(decoy: Decoy) -> None:
81
+
...
82
+
```
83
+
84
+
### Create a mock
85
+
86
+
Use `decoy.create_decoy` to create a mock based on some specification. From there, inject the mock into your test subject.
87
+
88
+
```python
89
+
deftest_add_todo(decoy: Decoy) -> None:
90
+
todo_store = decoy.create_decoy(spec=TodoStore)
91
+
subject = TodoAPI(store=todo_store)
92
+
...
93
+
```
94
+
95
+
See [creating mocks][] for more details.
96
+
97
+
### Stub a behavior
98
+
99
+
Use `decoy.when` to configure your mock's behaviors. For example, you can set the mock to return a certain value when called in a certain way using `then_return`:
100
+
101
+
```python
102
+
deftest_add_todo(decoy: Decoy) -> None:
103
+
"""Adding a todo should create a TodoItem in the TodoStore."""
104
+
todo_store = decoy.create_decoy(spec=TodoStore)
105
+
subject = TodoAPI(store=todo_store)
106
+
107
+
decoy.when(
108
+
todo_store.add(name="Write a test for adding a todo")
109
+
).then_return(
110
+
TodoItem(id="abc123", name="Write a test for adding a todo")
111
+
)
112
+
113
+
result = subject.add("Write a test for adding a todo")
114
+
assert result == TodoItem(id="abc123", name="Write a test for adding a todo")
115
+
```
116
+
117
+
See [stubbing with when][] for more details.
118
+
119
+
### Verify a call
120
+
121
+
Use `decoy.verify` to assert that a mock was called in a certain way. This is best used with dependencies that are being used for their side-effects and don't return a useful value.
122
+
123
+
```python
124
+
deftest_remove_todo(decoy: Decoy) -> None:
125
+
"""Removing a todo should remove the item from the TodoStore."""
0 commit comments