Skip to content

Commit 3193808

Browse files
authored
feat(pytest_plugin): add pytest plugin for decoy fixture injection (#26)
Closes #25
1 parent d7ebd35 commit 3193808

File tree

5 files changed

+41
-8
lines changed

5 files changed

+41
-8
lines changed

README.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,26 +41,25 @@ poetry add --dev decoy
4141

4242
## Setup
4343

44-
You'll want to create a test fixture to reset Decoy state between each test run. In [pytest][], you can do this by using a fixture to create a new Decoy instance for every test.
44+
### Pytest
4545

46-
The examples below assume the following global test fixture:
46+
Decoy ships with its own [pytest][] plugin, so once Decoy is installed, you're ready to start using it via its pytest fixture, called `decoy`.
4747

4848
```python
49-
import pytest
49+
# test_my_thing.py
5050
from decoy import Decoy
5151

52-
@pytest.fixture
53-
def decoy() -> Decoy:
54-
return Decoy()
52+
def test_my_thing_works(decoy: Decoy) -> None:
53+
# ...
5554
```
5655

57-
Why is this important? The `Decoy` container tracks every test double that is created during a test so that you can define assertions using fully-typed rehearsals. It's important to wipe this slate clean for every test so you don't leak memory or have any state preservation between tests.
56+
The `decoy` fixture is function-scoped and will ensure that all stub and spy state is reset between every test.
5857

5958
[pytest]: https://docs.pytest.org/
6059

6160
### Mypy Setup
6261

63-
Decoy's rehearsal syntax can be a bit confusing to [mypy][] if the mock in question is supposed to return `None`. Normally, [mypy will complain][] if you try to use a `None`-returning expression as a value, because this is almost always a mistake.
62+
Decoy's rehearsal syntax can be a bit confusing to [mypy][] if a function is supposed to return `None`. Normally, [mypy will complain][] if you try to use a `None`-returning expression as a value, because this is almost always a mistake.
6463

6564
In Decoy, however, it's an intentional part of the API and _not_ a mistake. To suppress these errors, Decoy provides a mypy plugin that you should add to your configuration file:
6665

decoy/pytest_plugin.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""Pytest plugin to setup and teardown a Decoy plugin."""
2+
import pytest
3+
from decoy import Decoy
4+
5+
6+
@pytest.fixture
7+
def decoy() -> Decoy:
8+
"""Get a Decoy test double container."""
9+
return Decoy()

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ pytest-asyncio = "^0.15.1"
3535
pytest-mypy-plugins = "^1.6.1"
3636
pytest-xdist = "^2.1.0"
3737

38+
[tool.poetry.plugins."pytest11"]
39+
"decoy" = "decoy.pytest_plugin"
40+
3841
[tool.pytest.ini_options]
3942
addopts = "--color=yes --mypy-ini-file=tests/typing/mypy.ini"
4043

tests/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import pytest
33
from decoy import Decoy
44

5+
pytest_plugins = ["pytester"]
6+
57

68
@pytest.fixture
79
def decoy() -> Decoy:

tests/test_pytest_plugin.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Tests for Decoy's pytest plugin."""
2+
from pytest import Testdir
3+
4+
5+
def test_pytest_decoy_fixture(testdir: Testdir) -> None:
6+
"""It should add a decoy test fixture."""
7+
# create a temporary pytest test file
8+
testdir.makepyfile(
9+
"""
10+
from decoy import Decoy
11+
12+
def test_decoy(decoy):
13+
assert isinstance(decoy, Decoy)
14+
"""
15+
)
16+
17+
result = testdir.runpytest()
18+
19+
# check that all 4 tests passed
20+
result.assert_outcomes(passed=1)

0 commit comments

Comments
 (0)