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

path

str

(required)

URL path (or pattern) this handler answers. See Path matching.

status_code

int

200

HTTP status code returned by MockResponse.status_code.

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

mocked_object

The API client object whose method should be patched.

method

Name of the method to patch, typically 'get'.

connection

Connection config object from config.yaml.

source

Source config object from config.yaml.

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

.json()

Returns the value returned by the decorated handler function.

.status_code

The status_code argument passed to @mock_response (default 200).

⚠️ 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

MOCK_RESULTS

.env, config.yaml connection params, or environment

"true" to enable

# config.yaml
connection:
  params:
    MOCK_RESULTS: "true"
# .env
MOCK_RESULTS=true