declarative-mocks
Declare mock behavior as ordered expectations. Write readable and maintainable tests with explicit call patterns, return sequences, and side effects using dmock.
It is fully documented. Public methods and matchers ship with docstring examples, and pretty much everything about API and behavior is described in REFERENCE.md and SPEC.md.
Installation
pip install declarative-mocks
dmock requires Python 3.11+.
Usage
Normally you would set a return value, then check the object field by field and assert the call separately:
from dataclasses import dataclass
from unittest.mock import Mock
@dataclass
class User:
id: int
name: str
email: str
class Directory:
def lookup(self, name: str) -> User: ...
service = Mock(spec=Directory)
service.lookup.return_value = User(id=1, name="Ada", email="ada@example.com")
user = service.lookup("ada")
assert isinstance(user.id, int)
assert user.name.startswith("A")
assert "@" in user.email
service.lookup.assert_called_once_with("ada")
Here's an example of how dmock improves your tests:
from dmock import AnythingOfType, Contains, DeclarativeMock, StartsWith
service = DeclarativeMock(Directory)
service.expect("lookup", "ada").returns(User(id=1, name="Ada", email="ada@example.com"))
assert service.lookup("ada") == User(
id=AnythingOfType(int),
name=StartsWith("A"),
email=Contains("@"),
)
service.verify()
That's not all! dmock can do so much more than that, for example:
Successive outcomes apply to the next matching call, in order:
from dmock import DeclarativeMock
api = DeclarativeMock(ApiClient)
api.expect("next_page").returns({"items": [1], "cursor": "a"})
api.expect("next_page").returns({"items": [2], "cursor": None})
# first call → first dict; second call → second dict
calc = DeclarativeMock(Calculator)
calc.expect("price", 100).runs(lambda x: x - 1).returns(99)
assert calc.price(100) == 99 # from the lambda
assert calc.price(100) == 99 # from .returns(99)
expect(...) with no further arguments matches a call with no arguments. A keyword matches only that keyword:
from dmock import Anything, DeclarativeMock
m = DeclarativeMock(Worker)
m.expect("run") # called with no arguments
m.expect("run", Anything()).returns(0) # any single positional arg
m.expect("run", job_id=123).returns("queued") # this keyword only
ANY_ARGS and ANY_KWARGS match any positional and keyword arguments, including none:
from dmock import ANY_ARGS, ANY_KWARGS, DeclarativeMock
m = DeclarativeMock(Worker)
m.expect("run", ANY_ARGS, ANY_KWARGS).returns(0) # any positional and keyword args, including none
Only one quantifier per expectation is allowed. The last outcome repeats when the count allows more calls, and verify() checks the counts:
from dmock import DeclarativeMock
calc = DeclarativeMock(Calculator)
calc.expect("price", 100).returns(99).once()
assert calc.price(100) == 99
calc.expect("price", 200).returns(99).times(3) # last outcome repeats for the extra calls
assert [calc.price(200) for _ in range(3)] == [99, 99, 99]
calc.expect("price", 300).returns(99).between(1, 3)
assert calc.price(300) == 99
calc.expect("price", 400).never()
calc.verify()
Async methods use the same DSL. runs() accepts a sync or an async functions:
from dmock import Anything, DeclarativeMock
mock = DeclarativeMock(MyService)
mock.expect("aprocess_order", Anything()).returns("ok")
assert await mock.aprocess_order(1) == "ok"
mock.expect("aprocess_order", Anything()).runs(lambda order_id: f"sync-{order_id}")
assert await mock.aprocess_order(7) == "sync-7"
async def async_fn(order_id: int) -> str:
return f"async-{order_id}"
mock.expect("aprocess_order", Anything()).runs(async_fn)
assert await mock.aprocess_order(9) == "async-9"
dmock is built on unittest.mock, has zero runtime dependencies, and doesn't patch anything. Unregistered calls fail immediately. Nothing on live modules or instances is rewritten.
Look up the dmock source code and tests yourself, it's full of examples!
Limitations & non-goals
- No signature binding.
expect("method", …)checks thatmethodexists on the spec, not that the argument list matches the real signature. - No automatic
verify(). You callverify()yourself (a pytest hook is on the roadmap). - No spies and no patching. The library does not wrap live objects, modules, or
sys.modules. Just construct aDeclarativeMockand pass it in. - Not thread-safe. Concurrent use of one mock from multiple threads is out of scope.
Roadmap
After 0.2.0, in no particular order:
- pytest auto-
verify()- a plugin or fixture that runsverify()at the end of each test (gomock-styleFinishon teardown), so you do not have to remember the call. - Per-outcome counts (maybe) - repeat a
.returns()/.raises()/.runs()without chaining it n times (today.returns(1).returns(1).returns(1).returns(2)).
Other ideas belong in GitHub Issues.
Development
poetry install
ruff check .
ruff format --check .
mypy src tests
pytest
pytest --cov --cov-report=term-missing
See CONTRIBUTING.md for setup, commits, and tests. Agents follow AGENTS.md.
License
MIT
Metadata
Release files for declarative-mocks 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| declarative_mocks-0.2.0.tar.gz | 20.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| declarative_mocks-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 40.9 kB
Release files / declarative_mocks-0.2.0.tar.gz
| Download URL | declarative_mocks-0.2.0.tar.gz |
|---|---|
| Size | 20.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
125088a726298deabb0470da0098e9ee7ee0d19c72c8af8d6f806ff4b450e700
|
|
BLAKE2b-256 checksum How to use checksums |
5c3ac9d053649d998413ee5552956a5bb275f068ca5e6734aec88e91d9ecf21e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / declarative_mocks-0.2.0-py3-none-any.whl
| Download URL | declarative_mocks-0.2.0-py3-none-any.whl |
|---|---|
| Size | 20.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
47734760ffcb8e58178e5497d17dc635c449d7bb39749eca12749eabb18d14da
|
|
BLAKE2b-256 checksum How to use checksums |
434ccf20e55680ac2a20cc030db8f4ddd951fbaae58ca3894751d7c73fbc6cb1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logSecurity for declarative-mocks 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for declarative_mocks-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.