{"id":15825395,"url":"https://github.com/mcous/decoy","last_synced_at":"2026-02-05T19:03:01.313Z","repository":{"id":37885203,"uuid":"315407244","full_name":"mcous/decoy","owner":"mcous","description":"🦆 Opinionated mocking library for Python","archived":false,"fork":false,"pushed_at":"2025-04-01T05:37:58.000Z","size":1363,"stargazers_count":28,"open_issues_count":6,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-06T14:16:32.359Z","etag":null,"topics":["magicmock","mock","mockito","spy","stub","tdd","test","testdouble"],"latest_commit_sha":null,"homepage":"https://michael.cousins.io/decoy/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mcous.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-11-23T18:37:00.000Z","updated_at":"2025-03-19T20:21:19.000Z","dependencies_parsed_at":"2024-02-04T07:26:18.917Z","dependency_job_id":"915b32bd-5dbf-42fb-af3f-4da367ed3021","html_url":"https://github.com/mcous/decoy","commit_stats":{"total_commits":219,"total_committers":5,"mean_commits":43.8,"dds":0.5616438356164384,"last_synced_commit":"1654d65ec3e5f83f1b2c51478a12fccb294d26b6"},"previous_names":[],"tags_count":35,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcous%2Fdecoy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcous%2Fdecoy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcous%2Fdecoy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcous%2Fdecoy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mcous","download_url":"https://codeload.github.com/mcous/decoy/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247492545,"owners_count":20947545,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["magicmock","mock","mockito","spy","stub","tdd","test","testdouble"],"created_at":"2024-10-05T09:09:14.626Z","updated_at":"2026-02-05T19:03:01.306Z","avatar_url":"https://github.com/mcous.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n    \u003cimg alt=\"Decoy logo\" src=\"https://michael.cousins.io/decoy/img/decoy.png\" width=\"256px\"\u003e\n    \u003ch1 class=\"decoy-title\"\u003eDecoy\u003c/h1\u003e\n    \u003cp\u003eOpinionated mocking library for Python\u003c/p\u003e\n    \u003cp\u003e\n        \u003ca title=\"CI Status\" href=\"https://github.com/mcous/decoy/actions\"\u003e\n        \u003cimg src=\"https://img.shields.io/github/actions/workflow/status/mcous/decoy/ci.yml?branch=main\u0026style=flat-square\"\u003e\u003c/a\u003e\n        \u003ca title=\"Code Coverage\" href=\"https://app.codecov.io/gh/mcous/decoy/\"\u003e\u003cimg src=\"https://img.shields.io/codecov/c/github/mcous/decoy?style=flat-square\"\u003e\u003c/a\u003e\n        \u003ca title=\"License\" href=\"https://github.com/mcous/decoy/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/mcous/decoy?style=flat-square\"\u003e\u003c/a\u003e\n        \u003ca title=\"PyPI Version\"href=\"https://pypi.org/project/decoy/\"\u003e\u003cimg src=\"https://img.shields.io/pypi/v/decoy?style=flat-square\"\u003e\u003c/a\u003e\n        \u003ca title=\"Supported Python Versions\" href=\"https://pypi.org/project/decoy/\"\u003e\u003cimg src=\"https://img.shields.io/python/required-version-toml?style=flat-square\u0026tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2Fmcous%2Fdecoy%2Fmain%2Fpyproject.toml\"\u003e\u003c/a\u003e\n    \u003c/p\u003e\n    \u003cp\u003e\n        \u003ca href=\"https://michael.cousins.io/decoy/\" class=\"decoy-hidden\"\u003eUsage guide and documentation\u003c/a\u003e\n    \u003c/p\u003e\n\u003c/div\u003e\n\nDecoy 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!\n\nDecoy 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.\n\n## Install\n\n```bash\npip install decoy\n```\n\n## Setup\n\n### Pytest setup\n\nDecoy ships with its own [pytest][] plugin, so once Decoy is installed, you're ready to start using it via its pytest fixture, called `decoy`.\n\n```python\n# test_my_thing.py\nfrom decoy import Decoy\n\ndef test_my_thing_works(decoy: Decoy) -\u003e None:\n    ...\n```\n\n### Mypy setup\n\nBy default, Decoy is compatible with Python [typing][] and type-checkers like [mypy][]. However, stubbing functions that return `None` can trigger a [type checking error](https://mypy.readthedocs.io/en/stable/error_code_list.html#check-that-called-function-returns-a-value-func-returns-value) during correct usage of the Decoy API. To suppress these errors, add Decoy's plugin to your mypy configuration.\n\n```ini\n# mypy.ini\nplugins = decoy.mypy\n```\n\n### Other testing libraries\n\nDecoy 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:\n\n1. Create a new instance of [`Decoy()`](https://michael.cousins.io/decoy/api/#decoy.Decoy) before each test\n2. Call [`decoy.reset()`](https://michael.cousins.io/decoy/api/#decoy.Decoy.reset) after each test\n\nFor 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`](https://github.com/mcous/decoy/blob/main/tests/test_unittest.py).\n\n## Basic Usage\n\nThis basic example assumes you are using [pytest][]. For more detailed documentation, see Decoy's [usage guide][] and [API reference][].\n\nDecoy will add a `decoy` fixture to pytest that provides its mock creation API.\n\n```python\nfrom decoy import Decoy\n\ndef test_something(decoy: Decoy) -\u003e None:\n    ...\n```\n\n!!! note\n\n    Importing the `Decoy` interface for type annotations is recommended, but optional. If your project does not use type annotations, you can simply write:\n\n    ```python\n    def test_something(decoy):\n        ...\n    ```\n\n### Create a mock\n\nUse `decoy.mock` to create a mock based on some specification. From there, inject the mock into your test subject.\n\n```python\ndef test_add_todo(decoy: Decoy) -\u003e None:\n    todo_store = decoy.mock(cls=TodoStore)\n    subject = TodoAPI(store=todo_store)\n    ...\n```\n\nSee [creating mocks][] for more details.\n\n### Stub a behavior\n\nUse `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`:\n\n```python\ndef test_add_todo(decoy: Decoy) -\u003e None:\n    \"\"\"Adding a todo should create a TodoItem in the TodoStore.\"\"\"\n    todo_store = decoy.mock(cls=TodoStore)\n    subject = TodoAPI(store=todo_store)\n\n    decoy.when(\n        todo_store.add(name=\"Write a test for adding a todo\")\n    ).then_return(\n        TodoItem(id=\"abc123\", name=\"Write a test for adding a todo\")\n    )\n\n    result = subject.add(\"Write a test for adding a todo\")\n    assert result == TodoItem(id=\"abc123\", name=\"Write a test for adding a todo\")\n```\n\nSee [stubbing with when][] for more details.\n\n### Verify a call\n\nUse `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.\n\n```python\ndef test_remove_todo(decoy: Decoy) -\u003e None:\n    \"\"\"Removing a todo should remove the item from the TodoStore.\"\"\"\n    todo_store = decoy.mock(cls=TodoStore)\n    subject = TodoAPI(store=todo_store)\n\n    subject.remove(\"abc123\")\n\n    decoy.verify(todo_store.remove(id=\"abc123\"), times=1)\n```\n\nSee [spying with verify][] for more details.\n\n[testdouble.js]: https://github.com/testdouble/testdouble.js\n[mockito]: https://site.mockito.org/\n[pytest]: https://docs.pytest.org/\n[unittest]: https://docs.python.org/3/library/unittest.html\n[typing]: https://docs.python.org/3/library/typing.html\n[mypy]: https://mypy.readthedocs.io/\n[api reference]: https://michael.cousins.io/decoy/api/\n[usage guide]: https://michael.cousins.io/decoy/usage/create/\n[creating mocks]: https://michael.cousins.io/decoy/usage/create/\n[stubbing with when]: https://michael.cousins.io/decoy/usage/when/\n[spying with verify]: https://michael.cousins.io/decoy/usage/verify/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmcous%2Fdecoy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmcous%2Fdecoy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmcous%2Fdecoy/lists"}