Mock Framework Reference¶
The mock framework lives in oaa.hooks.mock_responses. It provides a
decorator-based system for registering fake HTTP responses, activated via
the MOCK_RESULTS configuration flag.
For a step-by-step guide, see How to Mock API Responses for Local Development.
@mock_response¶
mock_response(path: str, status_code: int = 200)
Decorator that registers the decorated function as the mock handler for
path. The handler is stored in the global MOCK_RESPONSES registry at
decoration time (i.e. when the module is imported).
Parameters
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
|
(required) |
URL path (or pattern) this handler answers. See Path matching. |
|
|
|
HTTP status code returned by |
Return value of the decorated function
The function must return a JSON-serialisable value (dict, list, etc.). The
framework wraps it in a MockResponse automatically.
Example
from oaa.hooks.mock_responses import mock_response
@mock_response(path='/api/users')
def mock_users():
return {'users': [{'id': '1', 'name': 'Alice'}]}
@mock_response(path='/api/gone', status_code=410)
def mock_gone():
return {}
maybe_mock_it¶
maybe_mock_it(mocked_object, method: str, connection, source)
Conditionally replaces mocked_object.<method> with a
unittest.mock.MagicMock whose side_effect is get_sideeffect.
Mocking is activated when the MOCK_RESULTS parameter (checked via
params_or_env) resolves to the string "true" (case-insensitive).
Parameters
Parameter |
Description |
|---|---|
|
The API client object whose method should be patched. |
|
Name of the method to patch, typically |
|
Connection config object from |
|
Source config object from |
Example
from oaa.hooks.mock_responses import maybe_mock_it
api = MyAppAPI(connection, source)
maybe_mock_it(api, 'get', connection, source)
# api.get is now a MagicMock when MOCK_RESULTS=true
current_request_url¶
from oaa.hooks.mock_responses import current_request_url
url: str = current_request_url.get()
A contextvars.ContextVar[str] that holds the full URL passed to the
mocked method during the current call. It is set by get_sideeffect before
any handler is invoked, so it is always available inside a @mock_response
handler.
Default value (when called outside a mock context): ''.
Example
import urllib.parse
from oaa.hooks.mock_responses import mock_response, current_request_url
@mock_response(path='/report/*')
def report_handler():
url = current_request_url.get()
qs = urllib.parse.parse_qs(urllib.parse.urlparse(url).query)
entity_type = qs.get('entityType', ['All'])[0]
return MOCK_DATA.get(entity_type, [])
Path matching¶
get_sideeffect resolves the incoming URL using a four-tier fallback.
The first match wins.
1 — Exact full URL¶
The complete URL string including scheme, host, path, and query string.
@mock_response(path='https://api.example.com/v1/users?page=2')
def handler():
return {}
2 — Path + query string¶
Path and query only, no scheme or host.
@mock_response(path='/v1/users?status=active')
def active_users():
return {}
Multiple registrations for the same path but different query strings are all respected. The most specific match (path + query) is tried before the path-only fallback.
3 — Path only¶
Bare path with no query string. This is the original behaviour and matches any incoming URL that shares the same path, regardless of query parameters.
@mock_response(path='/v1/users')
def users():
return {}
4 — Wildcard (* and ?)¶
* matches any sequence of characters within a single path segment
(does not cross /). ? matches exactly one character.
@mock_response(path='/v1/users/*') # matches /v1/users/42
def user_detail():
return {}
@mock_response(path='/v1/???/users') # matches /v1/abc/users
def scoped_users():
return {}
5 — Regex (re: prefix)¶
Prefix the path with re: to match using re.fullmatch against the
path portion of the URL (scheme and host are excluded from the match).
@mock_response(path='re:/v1/users/[0-9]+')
def user_by_id():
return {}
@mock_response(path='re:/v1/.*/permissions')
def any_permissions():
return {}
Regex patterns are matched against the path-only portion (e.g.
/v1/users/42). Regex and wildcard keys are scanned in registration order;
exact matches always take priority.
MockResponse¶
class MockResponse:
def json(self, *args) -> Any: ...
status_code: int
A lightweight response wrapper returned by every mock handler. It mimics
the interface of a requests.Response object.
Attribute / Method |
Description |
|---|---|
|
Returns the value returned by the decorated handler function. |
|
The |
⚠️ Important: MockResponse Does NOT Implement raise_for_status()¶
MockResponse is a lightweight wrapper that mimics a requests.Response object, but it does NOT implement the raise_for_status() method. This means code that works in mock mode will fail silently in production mode.
Incorrect (works in mocks, fails in production):
resp = api.get(url)
resp.raise_for_status() # ❌ AttributeError: 'MockResponse' has no attribute 'raise_for_status'
Correct (works in both mock and live modes):
resp = api.get(url)
if resp.status_code >= 400:
raise RuntimeError(f"API Error: {resp.status_code}")
Always use explicit status code checks instead of raise_for_status() to ensure error handling is consistent across mock and live testing.
OAAMockException¶
Raised by get_sideeffect and call_response when no registered mock
matches the requested path.
from oaa.hooks.mock_responses import OAAMockException
try:
get_sideeffect('/unmocked/path')
except OAAMockException as e:
print(e) # no overridden path provided for /unmocked/path
MOCK_RESPONSES¶
MOCK_RESPONSES: dict[str, Callable]
The global registry mapping path keys to wrapped handler functions.
Populated automatically by @mock_response at import time.
In tests, clear this between test cases to avoid cross-test interference:
import pytest
from oaa.hooks.mock_responses import MOCK_RESPONSES
@pytest.fixture(autouse=True)
def clear_registry():
MOCK_RESPONSES.clear()
yield
MOCK_RESPONSES.clear()
Configuration¶
Setting |
Where |
Value |
|---|---|---|
|
|
|
# config.yaml
connection:
params:
MOCK_RESULTS: "true"
# .env
MOCK_RESULTS=true