Decoy is a mocking library designed for effective and productive test-driven development in Python. If you want to use tests to guide the structure of your code, Decoy might be for you!
Decoy mocks are async/await and type-checking friendly. Decoy is heavily inspired by (and/or stolen from) the excellent testdouble.js and Mockito projects. The Decoy API is powerful, easy to read, and strives to help you make good decisions about your code.
pip install decoyDecoy ships with its own pytest plugin, so once Decoy is installed, you're ready to start using it via its pytest fixture, called decoy.
# test_my_thing.py
from decoy import Decoy
def test_my_thing_works(decoy: Decoy) -> None:
...By default, Decoy is compatible with Python typing and type-checkers like mypy. However, stubbing functions that return None can trigger a type checking error during correct usage of the Decoy API. To suppress these errors, add Decoy's plugin to your mypy configuration.
# mypy.ini
plugins = decoy.mypyDecoy works well with pytest, but if you use another testing library or framework, you can still use Decoy! You just need to do two things:
- Create a new instance of
Decoy()before each test - Call
decoy.reset()after each test
For example, using the built-in unittest framework, you would use the setUp fixture method to do self.decoy = Decoy() and the tearDown method to call self.decoy.reset(). For a working example, see tests/test_unittest.py.
This basic example assumes you are using pytest. For more detailed documentation, see Decoy's usage guide and API reference.
Decoy will add a decoy fixture to pytest that provides its mock creation API.
from decoy import Decoy
def test_something(decoy: Decoy) -> None:
...!!! note
Importing the `Decoy` interface for type annotations is recommended, but optional. If your project does not use type annotations, you can simply write:
```python
def test_something(decoy):
...
```
Use decoy.mock to create a mock based on some specification. From there, inject the mock into your test subject.
def test_add_todo(decoy: Decoy) -> None:
todo_store = decoy.mock(cls=TodoStore)
subject = TodoAPI(store=todo_store)
...See creating mocks for more details.
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:
def test_add_todo(decoy: Decoy) -> None:
"""Adding a todo should create a TodoItem in the TodoStore."""
todo_store = decoy.mock(cls=TodoStore)
subject = TodoAPI(store=todo_store)
decoy.when(
todo_store.add(name="Write a test for adding a todo")
).then_return(
TodoItem(id="abc123", name="Write a test for adding a todo")
)
result = subject.add("Write a test for adding a todo")
assert result == TodoItem(id="abc123", name="Write a test for adding a todo")See stubbing with when for more details.
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.
def test_remove_todo(decoy: Decoy) -> None:
"""Removing a todo should remove the item from the TodoStore."""
todo_store = decoy.mock(cls=TodoStore)
subject = TodoAPI(store=todo_store)
subject.remove("abc123")
decoy.verify(todo_store.remove(id="abc123"), times=1)See spying with verify for more details.
