Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write multiple tests to the same cassette #42

Open
joshtemple opened this issue Apr 30, 2020 · 6 comments
Open

Write multiple tests to the same cassette #42

joshtemple opened this issue Apr 30, 2020 · 6 comments

Comments

@joshtemple
Copy link

Is it possible to write multiple tests to the same cassette? If multiple tests are making the same API calls, it's inefficient to record them again and again, better to use a shared cassette.

This is possible with vcr.use_cassette, but doesn't seem to be possible with pytest.mark.vcr, since the default cassette name is always the test name.

For example:

@pytest.mark.vcr("tests/cassettes/shared_cassette.yaml")
def test_a(): ...

@pytest.mark.vcr("tests/cassettes/shared_cassette.yaml")
def test_b(): ...

In this example, you will still write new cassettes to tests/cassettes/test_a.yaml and tests/cassettes/test_b.yaml, rather than using the shared one.

@DevilXD
Copy link

DevilXD commented May 5, 2020

From what I can see by studying the code, you might be able to influence the "test function name -> file name" linking behavior by specifying a def default_cassette_name(request): fixture in your conftest.py. I am not entirely sure on this though, here is the source line that suggests this:

def default_cassette_name(request):

On L57, you can see the default vcr_config fixture that the docs suggest to overwrite the same way, so it may be worth a try.

@Stranger6667
Copy link
Collaborator

Indeed, a combination of new_episodes record mode and custom default_cassette_name should work, and unique requests (according to used matchers) will be written in the same cassette file. As I remember that this behavior was chosen to avoid ambiguity on request/response pairs distribution among multiple cassettes - so it always writes to the "default" cassette, which is indeed not always convenient. For example:

@pytest.mark.vcr("gitlab_api_success.yaml", "slack_api_success.yaml")
def test_something():
    requests.get(...)  # Gitlab API call
    requests.post(...)  # Slack API call
    requests.pos(...)  # Some test-specific network call

In this case, it is not unambiguously clear which request/response pair should go to what cassette. Or at least it might be clear for the developer, but I decided to avoid any kind of assumptions from the library point of view.
Nevertheless, I will be happy to improve user experience with recording cassettes and open to suggestions. What do you think if we will have a special CLI option that will define the name of the cassette to record? E.g. `--record-to=shared.yaml"

@DevilXD
Copy link

DevilXD commented May 6, 2020

I think it'd be just fine to make it always write to the first cassette specified, while reading from all specified. If someone would need more control than that instead, I suggest making it possible to specify cassette fixtures as test arguments, roughly like pytest currently does with parametrization.

Reference: https://docs.pytest.org/en/latest/example/parametrize.html#parametrizing-conditional-raising (follow the link there for argument inputs types too)

Example:

@pytest.mark.vcr(gitlab_cassette="gitlab.yaml", slack_cassette="slack.yaml")
def test_apis(gitlab_cassette, slack_cassette):  # specifying more than one cassette kwarg enforces having to use the context manager yourself
    with gitlab_cassette:  # specify exactly what cassette you want to use here
        requests.get(...)
    with slack_cassette:
        requests.post(...)

This would also allow on custom operations being done on a cassette, such as using .rewind() - I'm not aware of an easy possibility of being able to do that right now.

Other possible usages:

@pytest.mark.vcr("main.yaml")  # just the cassette name, no kwarg
def test_apis(cassette):  # default fixture name giving access to the cassette itself
    # not specifying the cassette name as a kwarg means the entire test is implicatively wrapped just like before
    requests.get(...)
    cassette.rewind()  # possible given the cassette itself now
    requests.get(...)
@pytest.mark.vcr(my_cassette="main.yaml")  # explicit kwarg
def test_apis(my_cassette):  # fixture name matches the kwarg
    with my_cassette:  # explicit kwarg enforces context manager usage (just like in the above case with two of those)
        requests.get(...)
    my_cassette.rewind()  # possible given the cassette itself now
    with my_cassette:
        requests.post(...)

@Diaoul
Copy link

Diaoul commented Jul 11, 2020

I like the idea of having more control over the cassettes being used.

@Diaoul
Copy link

Diaoul commented Jul 11, 2020

So here is my use case as it is slightly different from what this ticket was originally about: I want to isolate some recordings done in a fixture in a specific cassette so it is not recorded for every test running the fixture.

Example: a fixture that provides a logged in client for a specific API to other tests

# this I want in a specific cassette
@pytest.fixture(scope="module")
async def client():
    async with SomeClient() as client:
        await client.login("username", "password")
        yield client


# this can be on it's own cassette, without the login part
@pytest.mark.vcr
@pytest.mark.asyncio
async def test_complex_operation(client):
    response = await client.complex_operation()

    assert response.status_code == 200

@dogweather
Copy link

dogweather commented Dec 28, 2023

How about marking a class? I just tested this and it works great:

@pytest.mark.default_cassette("crawl-nav.yaml")
class TestCrawlNavPages:
    @pytest.mark.vcr
    def test_number_of_results(_):
        pages = web_crawler.crawl_nav_pages(URL)
        assert len(pages) == 24

    @pytest.mark.vcr
    def test_returns_the_home_page(_):
        pages = web_crawler.crawl_nav_pages(URL)
        assert URL in [page.url for page in pages]

    @pytest.mark.vcr
    def test_crawl_data(_):
        ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants