Skip to main content

Checks PyPI Python 3.11+ License: MIT

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)
# Using `Directory.lookup` helps a type checker to checks the call arguments and the values passed to `.returns` and `.runs`
service.expect(Directory.lookup, "ada").returns(User(id=1, name="Ada", email="ada@example.com"))
# or you could use the string form to produce a lenient expectation: any arguments, any outcome
# 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 function:

from dmock import Anything, DeclarativeMock

mock = DeclarativeMock(MyService)
mock.expect(MyService.aprocess_order, Anything()).returns("ok")
assert await mock.aprocess_order(1) == "ok"

mock.expect(MyService.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(MyService.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 at runtime. expect("method", …) checks that method exists on the spec, not that the argument list matches the real signature at runtime.
  • No automatic verify() outside pytest. The pytest plugin checks every mock when the test finishes. Other runners still need an explicit verify().
  • No spies and no patching. The library does not wrap live objects, modules, or sys.modules. Just construct a DeclarativeMock and pass it in.
  • Not thread-safe. Concurrent use of one mock from multiple threads is out of scope.

Roadmap

Nothing is planned for 0.5.0, feel free to suggest!

  • Other test runners (low priority) - unittest and similar runners do not verify mocks on teardown the way the pytest plugin does.

Other ideas belong in GitHub Issues.

Development

poetry install
ruff check .
ruff format --check .
mypy src tests
pytest
coverage run -m pytest
coverage report

See CONTRIBUTING.md for setup, commits, and tests. Agents follow AGENTS.md.

License

MIT

Metadata

Release files for declarative-mocks 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for declarative-mocks 0.4.0
File Size Uploaded Provenance
declarative_mocks-0.4.0.tar.gz 23.5 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for declarative-mocks 0.4.0
File Interpreter ABI Platform
declarative_mocks-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 48.3 kB

Release files / declarative_mocks-0.4.0.tar.gz

Download URL declarative_mocks-0.4.0.tar.gz
Size 23.5 kB
Tags Source
SHA-256 checksum
How to use checksums
f38b9a33bbeaff5fc482126f9b9067787e4be3eb9acbf633e80dbb308d50ab47
BLAKE2b-256 checksum
How to use checksums
9a3254bec8f37d313b31875c31db6bef72c9c819a34f1540986a9b2c6a6d63b6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 23, 2026.

Transparency log

Release files / declarative_mocks-0.4.0-py3-none-any.whl

Download URL declarative_mocks-0.4.0-py3-none-any.whl
Size 24.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
515a3be744cd974fa02f12e1612ebd125b3eb7bdb8f47090106ac70ee4bb79b1
BLAKE2b-256 checksum
How to use checksums
38b161ef79c73e97e4fe2232b8116c9f708dd106a32396e25c0ac2917b5b23f5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 23, 2026.

Transparency log

Security for declarative-mocks 0.4.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.

Select from 2 files

Attestation for declarative_mocks-0.4.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 23, 2026.

Transparency log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page