Skip to content

Feedback on responses._recorder / How to make it better #740

Open

Description

@last-partizan

This is awesome feature, and I started using it right away.

But, it's tedious to have separate code for creating responses. So, I'm using following plugin:

It allows to run pytest --update-responses and they will be saved to a file, and loaded when used without this argument.

Maybe, include this as plugin? I can make PR adding this, so we can make proper code review.

from __future__ import annotations

import pytest
import responses
from _pytest.config.argparsing import Parser
from pytest_cases import fixture
from responses._recorder import recorder


def pytest_addoption(parser: Parser):
    group = parser.getgroup("responses")
    group.addoption(
        "--update-responses",
        action="store_true",
        dest="update_responses",
        default=False,
        help="Don't mock responses, instead write actual responses to snapshot files.",
    )


@fixture
def mocked_responses():
    with responses.RequestsMock() as rsps:
        yield rsps


@fixture
def responses_snapshot(request: pytest.FixtureRequest):
    """
    Before using response you need to write snapshots.

    Run with `pytest --update-responses`.

    https://github.com/getsentry/responses#record-responses-to-files
    """

    update = request.config.getoption("update_responses")
    snapshot = _get_snapshot_file(request)

    if update:
        with recorder as r:
            yield r
            recorder.dump_to_file(
                file_path=snapshot,
                registered=r.get_registry().registered,
            )
    else:
        with responses.RequestsMock() as r:
            r._add_from_file(snapshot)
            yield r


def _get_snapshot_file(request: pytest.FixtureRequest):
    f = request.node.name.replace("[", "-").replace("]", "")
    p = request.path.parent / "__responses__/"
    p.mkdir(exist_ok=True)
    return p / f"{f}.yaml"

Activity

  1. moved this to Waiting for: Product Owner in GitHub Issues with 👀 3on Oct 6, 2024
  2. moved this from Waiting for: Product Owner to No status in GitHub Issues with 👀 3on Oct 8, 2024
  3. moved this to Waiting for: Product Owner in GitHub Issues with 👀 3on Mar 26, 2025
  4. nschloe commented on Mar 28, 2025

    @nschloe

    This

    f = request.node.name.replace("[", "-").replace("]", "")

    might produce a filename which is too long. A better alternative is perhaps to hash the request.node.nodeid and use that as a file stem.

  5. sswivell commented on Oct 1, 2026

    @sswivell

    Disclosure: I maintain MockRelay, a different record/replay tool. Not trying to redirect the work here — your plugin is the right shape for a single-process library. One design question that cost me real time building the other thing, in case it's useful for the bundled plugin.

    The hard part of "save responses to a file" isn't the saving, it's the identity.

    In MockRelay a fixture's ID is derived from the request that produced it — upstream, method, path, query, and a hash of the redacted body. The consequence: recording the same endpoint twice with different responses overwrites one file. There is no ambiguity, it is deterministic, and it silently loses the second response.

    That's the right default for a recorder whose job is "keep the fixture set current," and it's probably the wrong default for your plugin. In your snippet the filename is f"{f}.yaml" where f is request.node.name — so you're keying on the test node, which sidesteps the problem entirely by giving one snapshot per test. That's deliberate and good, but it means two tests hitting the same endpoint with different expected responses each get their own file, and you're never merging them. Whether that's a feature depends on what people are trying to fix with the plugin.

    The trade-off in your favour is that per-test keying means no collision is even possible, and re-recording is always a straight overwrite. The risk is that a user with a shared helper called from three tests gets three near-duplicate snapshots and no signal that they could be shared.

    Two concrete suggestions if you do bundle it:

    1. Make the recorded-response path part of the contract, not an implementation detail. If responses bundles a snapshot feature, the file location and naming should be specified and documented, because people will commit those files and write assertions against them. A layout that accidentally collides across tests or silently merges two calls into one is much worse than an obvious "these two calls hit the same key, second one replaced the first."

    2. Survive record="new_episodes". That mode is per-request, not per-session. A snapshot written under mixed mode contains both recorded and pre-registered responses, and on the next run they all come back as replayed. Anything that needs to know which is which later — freshness checks, "is this actually pinned by a test" assertions — breaks silently at exactly that boundary.

    Your mocked_responses fixture and the update/playback branch is the right shape. If you want a second pair of eyes on the naming scheme or want a test case for the new_episodes interaction, happy to help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions