Repository: mcous/decoy (language: python)

Issue:
feature: pytest plugin to insert decoy fixture
## Overview

If Decoy shipped with a Pytest plugin to provide a `decoy` fixture, this would make getting started with Decoy easier. Plus, it would make it easier to have default fixture include teardown 


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 0878d55..cec0673 100644
--- a/README.md
+++ b/README.md
@@ -41,26 +41,25 @@ poetry add --dev decoy
 
 ## Setup
 
-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.
+### Pytest
 
-The examples below assume the following global test fixture:
+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`.
 
 ```python
-import pytest
+# test_my_thing.py
 from decoy import Decoy
 
-@pytest.fixture
-def decoy() -> Decoy:
-    return Decoy()
+def test_my_thing_works(decoy: Decoy) -> None:
+    # ...
 ```
 
-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.
+The `decoy` fixture is function-scoped and will ensure that all stub and spy state is reset between every test.
 
 [pytest]: https://docs.pytest.org/
 
 ### Mypy Setup
 
-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.
+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.
 
 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:
 
diff --git a/pyproject.toml b/pyproject.toml
index 948897f..2c14661 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -35,6 +35,9 @@ pytest-asyncio = "^0.15.1"
 pytest-mypy-plugins = "^1.6.1"
 pytest-xdist = "^2.1.0"
 
+[tool.poetry.plugins."pytest11"]
+"decoy" = "decoy.pytest_plugin"
+
 [tool.pytest.ini_options]
 addopts = "--color=yes --mypy-ini-file=tests/typing/mypy.ini"
 


Test patch:
diff --git a/decoy/pytest_plugin.py b/decoy/pytest_plugin.py
new file mode 100644
index 0000000..d57e6c3
--- /dev/null
+++ b/decoy/pytest_plugin.py
@@ -0,0 +1,9 @@
+"""Pytest plugin to setup and teardown a Decoy plugin."""
+import pytest
+from decoy import Decoy
+
+
+@pytest.fixture
+def decoy() -> Decoy:
+    """Get a Decoy test double container."""
+    return Decoy()
diff --git a/tests/conftest.py b/tests/conftest.py
index 3137de7..a473087 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -2,6 +2,8 @@
 import pytest
 from decoy import Decoy
 
+pytest_plugins = ["pytester"]
+
 
 @pytest.fixture
 def decoy() -> Decoy:
diff --git a/tests/test_pytest_plugin.py b/tests/test_pytest_plugin.py
new file mode 100644
index 0000000..2a28339
--- /dev/null
+++ b/tests/test_pytest_plugin.py
@@ -0,0 +1,20 @@
+"""Tests for Decoy's pytest plugin."""
+from pytest import Testdir
+
+
+def test_pytest_decoy_fixture(testdir: Testdir) -> None:
+    """It should add a decoy test fixture."""
+    # create a temporary pytest test file
+    testdir.makepyfile(
+        """
+        from decoy import Decoy
+
+        def test_decoy(decoy):
+            assert isinstance(decoy, Decoy)
+        """
+    )
+
+    result = testdir.runpytest()
+
+    # check that all 4 tests passed
+    result.assert_outcomes(passed=1)