pytest-recording

repository·master·Indexed 20 days ago

https://github.com/kiwicom/pytest-recording

A pytest plugin powered by VCR.py for recording and replaying HTTP traffic to ensure tests are fast, deterministic, and isolated from external network dependencies. It provides decorators like @pytest.mark.vcr and @pytest.mark.block_network, CLI options for record modes (once, rewrite, all, none, new_episodes), and a vcr_config fixture for global or scoped VCR options. Version 0.13.4.

Tokens
4K
Snippets
14
Records
21
Agent score
70%

What's inside pytest-recording

  1. Block network access

    master

    To ensure tests do not make real network calls, use the @pytest.mark.block_network decorator or the --block-network CLI flag.

    Note: If VCR.py recording is enabled (via @pytest.mark.vcr), network access is not blocked for those specific tests.

    Features:

    • Supports socket-based transports and pycurl.
    • Allows specific hosts via allowed_hosts.

    Usage Examples:

    import pytest
    import requests
    
    # Block all network access
    @pytest.mark.block_network
    def test_no_network():
        assert requests.get("http://httpbin.org/get")
    
    # Allow specific hosts
    @pytest.mark.block_network(allowed_hosts=["httpbin.*"])
    def test_partial_access():
        assert requests.get("http://httpbin.org/get").text == '{"get": true}'
        with pytest.raises(RuntimeError, match=r"^Network is disabled$"):
            requests.get("http://example.com")

    CLI Example:

    pytest --record-mode=once --block-network --allowed-hosts=httpbin.*,localhost tests/
  2. How cassette file organization works

    master

    By default, pytest-recording organizes cassettes into a directory structure to prevent name collisions between different test modules.

    For a test file located at tests/test_users.py, cassettes will be stored in: tests/cassettes/test_users/

    Individual cassette filenames are derived from the test class name and test function name (e.g., TestUserClass.test_create.yaml). If no class is present, the function name is used.

  3. Priority of allowed_hosts configuration

    master

    When determining which hosts are allowed during network blocking, pytest-recording follows a specific priority order. The first source found is used:

    1. The allowed_hosts argument passed to the @pytest.mark.block_network marker.
    2. The --allowed-hosts CLI option.
    3. The allowed_hosts key provided in the vcr_config fixture.
  4. Use the `rewrite` record mode

    master

    When configuring pytest-recording, setting the record_mode to rewrite triggers a specific cleanup behavior:

    1. The library calculates the target cassette path.
    2. It attempts to delete the existing cassette file at that path.
    3. It automatically switches the internal record_mode to new_episodes to ensure the new recording starts fresh without erroring on the missing file.
  5. Loading multiple cassettes with CombinedPersister

    master

    The CombinedPersister is an internal mechanism used to support loading data from multiple cassette files. It allows the library to:

    • Load the primary cassette specified by the test.
    • Load additional cassettes provided via pytest markers (extra_paths).
    • Combine the requests and responses from all found cassettes into a single unified view for the test execution.

    Note: While it loads from multiple sources, it only saves the recording to the first (primary) cassette file.

  6. How pytest-recording manages cassette file paths

    master

    pytest-recording automatically manages cassette file names and locations based on your test structure.

    1. Filename Length Safety: If a generated default_cassette name (plus its extension) exceeds the system's maximum filename length (MAX_FILENAME_LEN), the library truncates the name and appends an MD5 hash to ensure uniqueness and prevent OS errors (e.g., prefix...hash.yaml).
    2. Path Transformation: The library uses a path_transformer to ensure cassettes are stored in the correct directory.
    3. Extra Cassettes via Markers: You can specify additional cassette files using pytest markers. These paths can be absolute or relative. Relative paths are resolved against the vcr_cassette_dir.
  7. Configure VCR settings via vcr_config fixture

    master

    Provide a dictionary via the vcr_config fixture to set global or scoped VCR options (e.g., filter_headers, ignore_hosts). The fixture can have any scope: session, package, module, or function.

    Configuration Priority (Low to High):

    1. vcr_config fixture
    2. Marks applied from broadest scope (session) to narrowest (function)
    import pytest
    
    @pytest.fixture(scope="module")
    def vcr_config():
        return {
            "filter_headers": ["authorization"],
            "ignore_hosts": ["169.254.169.254"],
        }
  8. Access the VCR cassette object in tests

    master

    You can inject the vcr fixture into your test function to inspect the cassette, such as checking the play_count.

    import pytest
    import requests
    
    @pytest.mark.vcr
    def test_call_count(vcr):
        assert requests.get("http://httpbin.org/get").text == '{"get": true}'
        assert requests.get("http://httpbin.org/ip").text == '{"ip": true}'
        assert vcr.play_count == 2
  9. Use pytest.mark.vcr to record and replay HTTP traffic

    master

    Use the @pytest.mark.vcr decorator to enable recording/replaying for a test. By default, cassettes are stored in cassettes/{module_name}/test_name.yaml.

    import pytest
    import requests
    
    @pytest.mark.vcr
    def test_single():
        assert requests.get("http://httpbin.org/get").text == '{"get": true}'
  10. Specify custom or multiple VCR cassettes

    master

    You can control which cassettes are used via decorators:

    • Use @pytest.mark.default_cassette("filename.yaml") to specify a custom name for the default cassette.
    • Use @pytest.mark.vcr("path1.yaml", "path2.yaml") to use multiple cassettes in addition to the default one.
    import pytest
    import requests
    
    # Custom filename
    @pytest.mark.default_cassette("example.yaml")
    @pytest.mark.vcr
    def test_default():
        assert requests.get("http://httpbin.org/get").text == '{"get": true}'
    
    # Multiple cassettes
    @pytest.mark.vcr("/path/to/ip.yaml", "/path/to/get.yaml")
    def test_multiple():
        assert requests.get("http://httpbin.org/get").text == '{"get": true}'
        assert requests.get("http://httpbin.org/ip").text == '{"ip": true}'
  11. Register custom VCR matchers via pytest_recording_configure

    master

    Use the pytest_recording_configure hook in your conftest.py to access the VCR instance. This allows you to register custom matchers, persisters, or other VCR.py extensions.

    # conftest.py
    
    def jurassic_matcher(r1, r2):
        assert r1.uri == r2.uri and "JURASSIC PARK" in r1.body
    
    def pytest_recording_configure(config, vcr):
        vcr.register_matcher("jurassic", jurassic_matcher)