Skip to content

StrictVerifier

StrictVerifier

StrictVerifier()

Central orchestrator: owns timeline, plugin registry, ContextVar routing.

Source code in src/bigfoot/_verifier.py
def __init__(self) -> None:
    self._plugins: list[BasePlugin] = []
    self._timeline: Timeline = Timeline()
    self._bigfoot_config: dict[str, Any] = load_bigfoot_config()
    self._auto_instantiate_plugins()

mock

mock(name, wraps=None)

Create or retrieve a named MockProxy. Lazily creates MockPlugin.

If wraps is provided, method calls on the proxy with an empty queue are delegated to the wrapped object instead of raising UnmockedInteractionError. The interaction is still recorded.

Source code in src/bigfoot/_verifier.py
def mock(self, name: str, wraps: object = None) -> "MockProxy":
    """Create or retrieve a named MockProxy. Lazily creates MockPlugin.

    If wraps is provided, method calls on the proxy with an empty queue
    are delegated to the wrapped object instead of raising
    UnmockedInteractionError. The interaction is still recorded.
    """
    from bigfoot._mock_plugin import MockPlugin

    # Find existing MockPlugin or create one
    mock_plugin: MockPlugin | None = None
    for plugin in self._plugins:
        if isinstance(plugin, MockPlugin):
            mock_plugin = plugin
            break
    if mock_plugin is None:
        mock_plugin = MockPlugin(self)

    return mock_plugin.get_or_create_proxy(name, wraps=wraps)

spy

spy(name, real)

Syntactic sugar for mock(name, wraps=real).

Creates a MockProxy that always delegates to real, recording every call on the timeline without requiring mock configurations.

Source code in src/bigfoot/_verifier.py
def spy(self, name: str, real: object) -> "MockProxy":
    """Syntactic sugar for mock(name, wraps=real).

    Creates a MockProxy that always delegates to real, recording every
    call on the timeline without requiring mock configurations.
    """
    return self.mock(name, wraps=real)

assert_interaction

assert_interaction(source, **expected)

Assert the next interaction matches source and expected fields.

Completeness enforcement: after matching by source_id, the interaction's plugin.assertable_fields() is called. Any field in the returned set that is absent from **expected raises MissingAssertionFieldsError immediately, before the field-value match check.

Source code in src/bigfoot/_verifier.py
def assert_interaction(
    self,
    source: _HasSourceId,
    **expected: object,
) -> None:
    """Assert the next interaction matches source and expected fields.

    Completeness enforcement: after matching by source_id, the interaction's
    plugin.assertable_fields() is called. Any field in the returned set that
    is absent from **expected raises MissingAssertionFieldsError immediately,
    before the field-value match check.
    """
    self._assert_no_active_sandbox()
    source_id: str = source.source_id

    if _any_order_depth.get() > 0:
        # Two-pass in_any_order: Pass 1 — find an interaction matching source_id only,
        # then enforce completeness. Pass 2 — verify full field-value match.
        candidate = self._timeline.find_any_unasserted(lambda i: i.source_id == source_id)
        if candidate is None:
            remaining = self._timeline.all_unasserted()
            hint = self._format_mismatch_error(
                source_id, expected, actual=None, remaining=remaining
            )
            raise InteractionMismatchError(
                expected={"source_id": source_id, **expected},
                actual=None,
                hint=hint,
            )
        # Completeness check against the candidate
        required_fields = candidate.plugin.assertable_fields(candidate)
        missing = required_fields - set(expected.keys())
        if missing:
            raise MissingAssertionFieldsError(missing_fields=frozenset(missing))
        # Pass 2 — full field-value match (searching all unasserted, not just candidate)
        interaction = self._timeline.find_any_unasserted(
            lambda i: i.source_id == source_id and i.plugin.matches(i, expected)
        )
        if interaction is None:
            remaining = self._timeline.all_unasserted()
            hint = self._format_mismatch_error(
                source_id, expected, actual=None, remaining=remaining
            )
            raise InteractionMismatchError(
                expected={"source_id": source_id, **expected},
                actual=None,
                hint=hint,
            )
    else:
        interaction = self._timeline.peek_next_unasserted()
        if interaction is None or interaction.source_id != source_id:
            remaining = self._timeline.all_unasserted()
            hint = self._format_mismatch_error(
                source_id, expected, actual=interaction, remaining=remaining
            )
            raise InteractionMismatchError(
                expected={"source_id": source_id, **expected},
                actual=interaction,
                hint=hint,
            )
        # Completeness enforcement: check for missing required fields
        required_fields = interaction.plugin.assertable_fields(interaction)
        missing = required_fields - set(expected.keys())
        if missing:
            raise MissingAssertionFieldsError(missing_fields=frozenset(missing))
        if not interaction.plugin.matches(interaction, expected):
            remaining = self._timeline.all_unasserted()
            hint = self._format_mismatch_error(
                source_id, expected, actual=interaction, remaining=remaining
            )
            raise InteractionMismatchError(
                expected={"source_id": source_id, **expected},
                actual=interaction,
                hint=hint,
            )

    self._timeline.mark_asserted(interaction)

in_any_order

in_any_order()

Context manager: assertions within block matched in any order.

IMPORTANT: in_any_order() relaxes ordering globally across ALL plugins. It is not possible to relax ordering for only one plugin type. Any assert_interaction() call within this block will match any unasserted interaction regardless of which plugin (mock or HTTP) recorded it.

Source code in src/bigfoot/_verifier.py
def in_any_order(self) -> "InAnyOrderContext":
    """Context manager: assertions within block matched in any order.

    IMPORTANT: in_any_order() relaxes ordering globally across ALL plugins.
    It is not possible to relax ordering for only one plugin type. Any
    assert_interaction() call within this block will match any unasserted
    interaction regardless of which plugin (mock or HTTP) recorded it.
    """
    self._assert_no_active_sandbox()
    return InAnyOrderContext()

verify_all

verify_all()

Run Enforcement 2 and 3. Called at teardown.

Source code in src/bigfoot/_verifier.py
def verify_all(self) -> None:
    """Run Enforcement 2 and 3. Called at teardown."""
    self._assert_no_active_sandbox()
    unasserted = self._timeline.all_unasserted()
    unused: list[tuple[BasePlugin, Any]] = []

    for plugin in self._plugins:
        for mock_config in plugin.get_unused_mocks():
            unused.append((plugin, mock_config))

    has_unasserted = bool(unasserted)
    has_unused = bool(unused)

    if not has_unasserted and not has_unused:
        return

    if has_unasserted and has_unused:
        raise VerificationError(
            unasserted=UnassertedInteractionsError(
                interactions=unasserted,
                hint=self._format_unasserted_error(unasserted),
            ),
            unused=UnusedMocksError(
                mocks=[mc for _, mc in unused],
                hint=self._format_unused_mocks_error(unused),
            ),
        )
    elif has_unasserted:
        raise UnassertedInteractionsError(
            interactions=unasserted,
            hint=self._format_unasserted_error(unasserted),
        )
    else:
        raise UnusedMocksError(
            mocks=[mc for _, mc in unused],
            hint=self._format_unused_mocks_error(unused),
        )

sandbox

sandbox()

Return SandboxContext for this verifier (sync + async).

Source code in src/bigfoot/_verifier.py
def sandbox(self) -> "SandboxContext":
    """Return SandboxContext for this verifier (sync + async)."""
    return SandboxContext(self)

SandboxContext

SandboxContext(verifier)

Activates all plugins. Supports both sync (with) and async (async with).

Source code in src/bigfoot/_verifier.py
def __init__(self, verifier: StrictVerifier) -> None:
    self._verifier = verifier
    self._token: contextvars.Token[Any] | None = None

InAnyOrderContext

InAnyOrderContext()

Context manager: assertions within block matched in any order.

Source code in src/bigfoot/_verifier.py
def __init__(self) -> None:
    self._token: contextvars.Token[int] | None = None