diff --git a/appveyor.yml b/appveyor.yml index 560c1be..47e3f9e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,6 +21,9 @@ init: - "%PYTHON%/python -c \"import struct;print( 8 * struct.calcsize(\'P\'))\"" install: + - "%PYTHON%/python -m pip install --upgrade pip" + - "%PYTHON%/python -m pip uninstall -y virtualenv" + - "%PYTHON%/Scripts/pip install virtualenv" - "%PYTHON%/Scripts/easy_install -U pip" - "%PYTHON%/Scripts/pip install tox" - "%PYTHON%/Scripts/pip install wheel" diff --git a/docs/configuration.md b/docs/configuration.md index ae8a991..98b1aab 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -55,7 +55,7 @@ def vcr(vcr): # Marker options -All options provided on the marker will be passed to the VCR constructor, for example: +All options (keyword arguments) provided on the marker will be passed to the VCR constructor, for example: ```python @pytest.mark.vcr(ignore_localhost=True) @@ -65,3 +65,12 @@ def test_local_and_remote(): # This one will always be downloaded urlopen('http://127.0.0.1/').read() ``` + +It is also posible to specify cassette name and path per test using path as marker first argument + +```python +@pytest.mark.vcr("custom/path/iana.yaml") +def test_local_and_remote(): + # This one will replay from the cassette + urlopen('http://www.iana.org/domains/reserved').read() +``` \ No newline at end of file diff --git a/pytest_vcr.py b/pytest_vcr.py index 250791f..73394e0 100644 --- a/pytest_vcr.py +++ b/pytest_vcr.py @@ -46,6 +46,10 @@ def _vcr_marker(request): request.getfixturevalue('vcr_cassette') +def _has_custom_name(marker): + return marker and marker.args + + def _update_kwargs(request, kwargs): marker = request.node.get_closest_marker('vcr') if marker: @@ -62,9 +66,8 @@ def _update_kwargs(request, kwargs): kwargs['before_record_response'] = lambda *args, **kwargs: None -@pytest.fixture(scope='module') -def vcr(request, vcr_config, vcr_cassette_dir, ): - """The VCR instance""" +def _vcr(request, vcr_config, vcr_cassette_dir, ): + """Create VCR instance""" if request.config.getoption('--vcr-record-mode'): warnings.warn("--vcr-record-mode has been deprecated and will be removed in a future " "release. Use --vcr-record instead.", @@ -79,11 +82,28 @@ def vcr(request, vcr_config, vcr_cassette_dir, ): return vcr +@pytest.fixture(scope='module') +def vcr(request, vcr_config, vcr_cassette_dir, ): + """The VCR instance, module level fixture""" + return _vcr(request, vcr_config, vcr_cassette_dir, ) + + +@pytest.fixture +def vcr_per_test(request, vcr_config): + """The VCR instance, function level fixture""" + return _vcr(request, vcr_config, None) + + @pytest.fixture -def vcr_cassette(request, vcr, vcr_cassette_name): +def vcr_cassette(request, vcr_cassette_name): """Wrap a test in a VCR.py cassette""" kwargs = {} _update_kwargs(request, kwargs) + marker = request.node.get_closest_marker("vcr") + if _has_custom_name(marker): + vcr = request.getfixturevalue("vcr_per_test") + else: + vcr = request.getfixturevalue("vcr") with vcr.use_cassette(vcr_cassette_name, **kwargs) as cassette: yield cassette @@ -97,6 +117,9 @@ def vcr_cassette_dir(request): @pytest.fixture def vcr_cassette_name(request): """Name of the VCR cassette""" + marker = request.node.get_closest_marker("vcr") + if _has_custom_name(marker): + return marker.args[0] test_class = request.cls if test_class: return "{}.{}".format(test_class.__name__, request.node.name) diff --git a/tests/test_vcr.py b/tests/test_vcr.py index 383603c..f391187 100644 --- a/tests/test_vcr.py +++ b/tests/test_vcr.py @@ -201,6 +201,30 @@ def test_method(self, vcr_cassette_name): result.stdout.fnmatch_lines(['*Cassette: TestClass.test_method']) +def test_cassette_custom_path(testdir): + testdir.makepyfile( + """ + import os + import pytest + + @pytest.mark.vcr("custom/path.yaml") + def test_cassette_name(vcr_cassette_name, vcr_cassette): + assert "custom/path.yaml" == vcr_cassette._path + print("Cassette name: {}".format(vcr_cassette_name)) + + @pytest.mark.vcr + def test_no_path(vcr_cassette_name, vcr_cassette): + assert os.path.join("cassettes", "test_no_path.yaml") in vcr_cassette._path + print("Cassette name: {}".format(vcr_cassette_name)) + """ + ) + + result = testdir.runpytest("-s") + result.assert_outcomes(2, 0, 0) + result.stdout.fnmatch_lines(['*Cassette name: custom/path.yaml']) + result.stdout.fnmatch_lines(['*Cassette name: test_no_path']) + + def test_vcr_config(testdir): testdir.makepyfile(""" import pytest @@ -237,6 +261,24 @@ def test_method(vcr_cassette): result.stdout.fnmatch_lines(['*Cassette record mode: none']) +def test_cassette_custom_path_marker_options(testdir): + testdir.makepyfile(""" + import pytest + + @pytest.fixture(scope='module') + def vcr_config(): + return {'record_mode': 'all'} + + @pytest.mark.vcr("custom/path.yaml", record_mode='none') + def test_method(vcr_cassette): + print("Cassette record mode: {}".format(vcr_cassette.record_mode)) + """) + + result = testdir.runpytest('-s') + result.assert_outcomes(1, 0, 0) + result.stdout.fnmatch_lines(['*Cassette record mode: none']) + + def test_overriding_record_mode(testdir): testdir.makepyfile(""" import pytest