pymocks
Testing utility decorators and context managers for mocking functions, variables, and HTTP endpoints in Python.
Works with both synchronous and asynchronous test functions. Sync/async is detected automatically. All mocking utilities can be used as decorators or context managers.
Installation
pip install pymocks
Or with uv:
uv add pymocks
Usage
Mocking Functions and Variables
Use Mock with with_mock to monkeypatch module attributes for the duration of a test. Accepts one or more Mock objects. Works as a decorator or context manager:
import my_module
from pymocks import Mock, with_mock
# Mock a function — the replacement must have the same signature
def fake_function(x: int, y: str) -> bool:
return True
mock = Mock(
module_where_used=my_module,
current_value=my_module.some_function,
new_value=fake_function,
)
@with_mock(mock)
def test_with_mocked_function():
result = my_module.some_function(1, "a")
assert result is True
# Mock a variable — the replacement must have the same type
var_mock = Mock(
module_where_used=my_module,
current_value=my_module.API_URL,
new_value="https://mock.example.com",
)
@with_mock(var_mock)
def test_with_mocked_variable():
assert my_module.API_URL == "https://mock.example.com"
Pass multiple mocks to apply them all at once — they are all reverted together when the scope exits:
@with_mock(mock, var_mock)
def test_with_multiple_mocks():
result = my_module.some_function(1, "a")
assert result is True
assert my_module.API_URL == "https://mock.example.com"
The same works for async tests:
@with_mock(mock)
async def test_async_with_mock():
result = my_module.some_function(1, "a")
assert result is True
Or use it as a context manager for more flexible scoping:
def test_with_context_manager():
with with_mock(mock, var_mock):
result = my_module.some_function(1, "a")
assert result is True
# mocks are reverted here
async def test_async_with_context_manager():
async with with_mock(mock):
result = my_module.some_function(1, "a")
assert result is True
Mocking Classes
Use Mock to replace a class with a subclass. The replacement must be a subclass of the original:
import my_module
from pymocks import Mock, with_mock
class FakeService(my_module.Service):
def fetch(self) -> str:
return "fake data"
mock = Mock(
module_where_used=my_module,
current_value=my_module.Service,
new_value=FakeService,
)
@with_mock(mock)
def test_with_mocked_class():
svc = my_module.Service()
assert svc.fetch() == "fake data"
Replacing a class with an unrelated class raises TypeError:
class Unrelated:
pass
# Raises TypeError — Unrelated is not a subclass of Service
Mock(
module_where_used=my_module,
current_value=my_module.Service,
new_value=Unrelated,
)
Signature and Type Validation
Mock validates compatibility between current_value and new_value at construction time:
- Callables: signatures must match exactly (parameter count, names, kinds, annotations, and return annotation)
- Classes:
new_valuemust be a subclass ofcurrent_value - Non-callables:
type(current_value)must be the same astype(new_value) - Mixed: replacing a callable with a non-callable (or vice versa) raises
TypeError
def original(x: int) -> str:
return str(x)
# Signature mismatch — raises TypeError immediately
Mock(
module_where_used=my_module,
current_value=original,
new_value=lambda x: str(x), # missing annotations
)
# Type mismatch — raises TypeError immediately
Mock(
module_where_used=my_module,
current_value="a string",
new_value=42,
)
Mocking HTTP Endpoints
Use MockAiohttpEndpoint with with_aiohttp_endpoints to mock HTTP calls via aioresponses. Works as a decorator or context manager:
import aiohttp
from pymocks import MockAiohttpEndpoint, with_aiohttp_endpoints
endpoints = (
MockAiohttpEndpoint(
url="https://api.example.com/users",
method="GET",
json_response={"users": [{"id": 1, "name": "Alice"}]},
),
MockAiohttpEndpoint(
url="https://api.example.com/users",
method="POST",
json_response={"id": 2, "name": "Bob"},
),
)
# As a decorator
@with_aiohttp_endpoints(endpoints)
async def test_api_calls():
async with aiohttp.ClientSession() as session:
async with session.get("https://api.example.com/users") as resp:
data = await resp.json()
assert len(data["users"]) == 1
# As a context manager
async def test_api_calls_ctx():
async with with_aiohttp_endpoints(endpoints):
async with aiohttp.ClientSession() as session:
async with session.get("https://api.example.com/users") as resp:
data = await resp.json()
assert len(data["users"]) == 1
Asserting Request Data
Use assert_request on a MockAiohttpEndpoint to inspect the URL, headers, body, and params sent by the code under test. The callback receives a single MockedAiohttpRequest — mirroring the assert_request(request) shape used by the httpx backend:
from pymocks import MockAiohttpEndpoint, MockedAiohttpRequest, with_aiohttp_endpoints
def check_request(request: MockedAiohttpRequest) -> None:
assert (request.headers or {})["Authorization"] == "Bearer token123"
assert request.json == {"name": "alice"}
endpoint = MockAiohttpEndpoint(
url="https://api.example.com/users",
method="POST",
json_response={"id": 1},
assert_request=check_request,
)
@with_aiohttp_endpoints((endpoint,))
async def test_post_sends_correct_data():
async with aiohttp.ClientSession() as session:
await session.post(
"https://api.example.com/users",
json={"name": "alice"},
headers={"Authorization": "Bearer token123"},
)
MockedAiohttpRequest is a frozen dataclass with the following fields:
| Field | Type | Description |
|---|---|---|
url |
yarl.URL |
Request URL |
headers |
dict[str, str] | None |
Request headers |
params |
dict[str, str] | None |
Query parameters |
data |
Any |
Raw request body |
json |
dict[str, JsonValue] | None |
JSON request body |
When assert_request is None (the default), the endpoint returns the configured response without any assertion callback.
Mocking httpx Endpoints
Use MockHttpxEndpoint with with_httpx_endpoints to mock httpx calls (both httpx.Client and httpx.AsyncClient) via pytest-httpx. Works as a decorator or context manager, sync or async:
import httpx
from pymocks import MockHttpxEndpoint, with_httpx_endpoints
endpoints = (
MockHttpxEndpoint(
url="https://api.example.com/users",
method="GET",
json_response={"users": [{"id": 1, "name": "Alice"}]},
),
MockHttpxEndpoint(
url="https://api.example.com/users",
method="POST",
json_response={"id": 2, "name": "Bob"},
status_code=201,
),
)
# Sync decorator
@with_httpx_endpoints(endpoints)
def test_api_calls():
with httpx.Client() as client:
data = client.get("https://api.example.com/users").json()
assert len(data["users"]) == 1
# Async context manager
async def test_api_calls_ctx():
async with (
with_httpx_endpoints(endpoints),
httpx.AsyncClient() as client,
):
response = await client.post(
"https://api.example.com/users", json={"name": "Bob"},
)
assert response.status_code == 201
Supported methods: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS.
The assert_request callback for an httpx endpoint receives the raw httpx.Request, so you can read headers, URL, and body directly:
import httpx
from pymocks import MockHttpxEndpoint, with_httpx_endpoints
def check_request(request: httpx.Request) -> None:
assert request.headers["Authorization"] == "Bearer token123"
assert request.read() == b'{"name":"alice"}'
endpoint = MockHttpxEndpoint(
url="https://api.example.com/users",
method="POST",
json_response={"id": 1},
assert_request=check_request,
)
@with_httpx_endpoints((endpoint,))
def test_post_sends_correct_data():
with httpx.Client() as client:
client.post(
"https://api.example.com/users",
json={"name": "alice"},
headers={"Authorization": "Bearer token123"},
)
Every registered endpoint must be called at least once or the block raises an AssertionError on exit — this matches pytest-httpx's default assertion behavior.
API Reference
Mock[T_mocked]
A dataclass that defines a monkeypatch specification. Validates compatibility on construction.
| Field | Type | Description |
|---|---|---|
module_where_used |
ModuleType |
The module containing the attribute to patch |
current_value |
T_mocked |
The current value (used to find its name) |
new_value |
T_mocked |
The replacement value during the test |
MockAiohttpEndpoint
A frozen dataclass defining an HTTP endpoint mock for aiohttp (via aioresponses).
| Field | Type | Description |
|---|---|---|
url |
str |
The URL to mock |
method |
Literal["GET", "POST", "PUT", "DELETE"] |
HTTP method |
json_response |
dict[str, JsonValue] | None |
JSON response body (optional) |
body |
str | None |
Raw string body (optional) |
assert_request |
Callable[[MockedAiohttpRequest], None] | None |
Request assertion callback (optional) |
MockHttpxEndpoint
A frozen dataclass defining an HTTP endpoint mock for httpx (via pytest-httpx).
| Field | Type | Description |
|---|---|---|
url |
str |
The URL to mock |
method |
Literal["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"] |
HTTP method |
json_response |
dict[str, JsonValue] | None |
JSON response body (optional) |
body |
str | None |
Raw string body (optional) |
status_code |
int |
HTTP status code (defaults to 200) |
assert_request |
Callable[[httpx.Request], None] | None |
Request assertion callback (optional) |
MockedAiohttpRequest
A frozen dataclass passed to MockAiohttpEndpoint.assert_request. Wraps the per-request data aioresponses supplies into a single argument, matching the assert_request(request) shape of the httpx backend.
| Field | Type | Description |
|---|---|---|
url |
yarl.URL |
Request URL |
headers |
dict[str, str] | None |
Request headers |
params |
dict[str, str] | None |
Query parameters |
data |
Any |
Raw request body |
json |
dict[str, JsonValue] | None |
JSON request body |
with_mock(*mocks) / with_aiohttp_endpoints(endpoints) / with_httpx_endpoints(endpoints)
All three can be used as decorators or context managers (sync and async):
# Decorator — single mock
@with_mock(mock)
def test_decorated(): ...
# Decorator — multiple mocks
@with_mock(mock, var_mock)
def test_decorated(): ...
# Sync context manager
with with_mock(mock, var_mock):
...
# Async context manager
async with with_mock(mock):
...
When used as decorators, sync/async is detected automatically.
Requirements
- Python >= 3.12
- pytest
- aioresponses
- pytest-httpx (for httpx endpoint mocking)
License
MIT
Metadata
Release files for py-mocks 0.5.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 | |
|---|---|---|---|
| py_mocks-0.5.0.tar.gz | 62.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| py_mocks-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 72.4 kB
Release files / py_mocks-0.5.0.tar.gz
| Download URL | py_mocks-0.5.0.tar.gz |
|---|---|
| Size | 62.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
78cda905a97ba71c17ca74743b0d06ffc1b97428cc15c90de312c07b72846826
|
|
BLAKE2b-256 checksum How to use checksums |
688383c2e112c2de78b6f83a22e6cfd96219acf585c7234679d377a8c441ad25
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / py_mocks-0.5.0-py3-none-any.whl
| Download URL | py_mocks-0.5.0-py3-none-any.whl |
|---|---|
| Size | 10.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
59d335d3b6449412c3bc08bf83280c8a6a17aaa73f82e645cb5cf00883e59e91
|
|
BLAKE2b-256 checksum How to use checksums |
61df99df52862e8b8fdbc24dc9cce1205ab4fc6a38506fe36fbcd0cad9de59d2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|