Open joshtemple opened 4 years ago
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: https://github.com/kiwicom/pytest-recording/blob/ffe27e5aa7c78dbc0d1eff012335704852cc0d27/src/pytest_recording/plugin.py#L109
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.
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"
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(...)
I like the idea of having more control over the cassettes being used.
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
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(_):
...
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 withpytest.mark.vcr
, since the default cassette name is always the test name.For example:
In this example, you will still write new cassettes to
tests/cassettes/test_a.yaml
andtests/cassettes/test_b.yaml
, rather than using the shared one.