kevin1024 / vcrpy

Automatically mock your HTTP interactions to simplify and speed up testing
MIT License
2.72k stars 388 forks source link

How do I assert on `cassette.all_played` conditionally based on wheter I record or replay? #750

Closed mezhaka closed 1 year ago

mezhaka commented 1 year ago

The following test fails when recording:

import vcr
import requests

def test_all_played():
    with vcr.use_cassette(path="tmp.yaml") as cassette:
        requests.get("https://google.com")
        assert cassette.all_played  # cassette.all_played is False during the recording

and works fine when replayed, which is a bit of a surprise for the people I work with. Is there any idomatic way of introspecting whether a cassette is being recorded vs. being played back? This way I could do something like:

import vcr
import requests

def test_all_played():
    with vcr.use_cassette(path="tmp.yaml") as cassette:
        requests.get("https://google.com")
        if cassete.is_being_played_back:
            assert cassette.all_played

Right now I ended up with:

import vcr
import os
import requests

RECORD_MODE = vcr.mode(os.environ.get("TEST_RECORD_MODE", "none"))  # defaults to vcr.mode.NONE

def test_all_played():
    with vcr.use_cassette(path="tmp.yaml") as cassette:
        requests.get("https://google.com")
        if RECORD_MODE == vcr.mode.NONE:
            assert cassette.all_played

Then if I want to record the tests, I run them with env TEST_RECORD_MODE=once python -m pytest .... Is there maybe another way of doing what I want, i.e. asserting everything has been played back or not trying to assert at all?

It would even be easier for me if this setting would be part of the use_cassette. For example, the use_cassette could raise NotAllPlayedError when exiting context manager in the playback mode, but do nothing in the replay. If this could be configurable from the use_cassette, it would allow me to use use_cassette as a function decorator (I would not even need the cassette instance):

def raise_(e):
    raise e

@vcr.use_cassette(on_not_all_played=lambda: raise_(NotAllPlayedError)):
def test_all_played():
    import requests
    requests.get("https://google.com")
mezhaka commented 1 year ago

I ended up using the cassette's write_protected method as a sign that I am in the recording mode:

import vcr
import requests

def test_all_played():
    with vcr.use_cassette(path="tmp.yaml") as cassette:
        requests.get("https://google.com")
        if cassete.write_protected:
            assert cassette.all_played

So far it seems to work.

Here's my pytest based setup:

@pytest.fixture
def myvcr() -> vcr.VCR:
    # The default "none" value of RECORD_MODE means the test will fail if there is no record for it
    # found--this is useful to prevent test from running in the CI if a recording has not been
    # checked in.
    record_mode = vcr.mode(os.environ.get("TEST_RECORD_MODE", "none"))
    return vcr.VCR(
        record_mode=record_mode,
        path_transformer=vcr.VCR.ensure_suffix(".yaml"),
        decode_compressed_response=True,
        filter_headers=["Authorization"],  # Parts of the request that will not be recorded.
        # TODO Switch from raw_body to body, when https://github.com/kevin1024/vcrpy/issues/749 is released.
        match_on=["method", "scheme", "host", "port", "path", "query", "raw_body"],
        record_on_exception=False,  # If exception is not caught within `use_cassette` context manager, the recording would fail.
    )

@pytest.fixture
def cassette_path(request: pytest.FixtureRequest) -> pathlib.Path:
    test_name = request.node.name
    return pathlib.Path(__file__).parent / "resources" / "cassettes" / (test_name + ".yaml")

@pytest.fixture
def cassette(cassette_path: pathlib.Path, myvcr: vcr.VCR) -> Iterator[vcr.cassette.Cassette]:
    with myvcr.use_cassette(path=str(cassette_path)) as cassette:
        yield cassette

def test_all_played(cassette):
    requests.get("https://google.com")
    if cassette.write_protected:
        assert cassette.all_played