diff --git a/README.md b/README.md index d3e6a944..03e76030 100644 --- a/README.md +++ b/README.md @@ -379,6 +379,17 @@ By overriding the provided [`AbstractSnapshotExtension`](https://github.com/toph See examples of how syrupy can be used and extended in the [test examples](https://github.com/tophat/syrupy/tree/main/tests/examples). +#### Overriding defaults + +It is possible to override `include`, `exclude`, `matchers` and `extension_class` on a more global level just once, +instead of every time per test. By default, after every assertion the modified values per snapshot assert are reverted +to their default values. However, it is possible to override those default values with ones you would like persisted, +which will be treated as the new defaults. + +To achieve that you can use `snapshot.with_defaults`, which will create new instance of `SnapshotAssertion` with the provided values. + +`snapshot.use_extension` is retained for compatibility. However, it is limited to only overriding the default extension class. + #### JSONSnapshotExtension This extension can be useful when testing API responses, or when you have to deal with long dictionaries that are cumbersome to validate inside a test. For example: @@ -390,7 +401,8 @@ from syrupy.extensions.json import JSONSnapshotExtension @pytest.fixture def snapshot_json(snapshot): - return snapshot.use_extension(JSONSnapshotExtension) + return snapshot.with_defaults(extension_class=JSONSnapshotExtension) + # or return snapshot.use_extension(JSONSnapshotExtension) def test_api_call(client, snapshot_json): @@ -455,6 +467,7 @@ The generated snapshot: ### Extending Syrupy +- [Custom defaults](https://github.com/tophat/syrupy/tree/main/tests/examples/test_custom_defaults.py) - [Custom snapshot directory 1](https://github.com/tophat/syrupy/tree/main/tests/examples/test_custom_snapshot_directory.py) - [Custom snapshot directory 2](https://github.com/tophat/syrupy/tree/main/tests/examples/test_custom_snapshot_directory_2.py) - [Custom snapshot name](https://github.com/tophat/syrupy/tree/main/tests/examples/test_custom_snapshot_name.py) diff --git a/src/syrupy/assertion.py b/src/syrupy/assertion.py index a3a7c0f8..6bdb4fcf 100644 --- a/src/syrupy/assertion.py +++ b/src/syrupy/assertion.py @@ -60,6 +60,9 @@ class SnapshotAssertion: extension_class: Type["AbstractSyrupyExtension"] test_location: "PyTestLocation" update_snapshots: bool + include: Optional["PropertyFilter"] = None + exclude: Optional["PropertyFilter"] = None + matcher: Optional["PropertyMatcher"] = None _exclude: Optional["PropertyFilter"] = field( init=False, @@ -99,6 +102,9 @@ class SnapshotAssertion: def __post_init__(self) -> None: self.session.register_request(self) + self._include = self.include + self._exclude = self.exclude + self._matcher = self.matcher def __init_extension( self, extension_class: Type["AbstractSyrupyExtension"] @@ -165,20 +171,37 @@ def _matcher(**kwargs: Any) -> Optional["SerializableData"]: return _matcher - def use_extension( - self, extension_class: Optional[Type["AbstractSyrupyExtension"]] = None + def with_defaults( + self, + *, + exclude: Optional["PropertyFilter"] = None, + include: Optional["PropertyFilter"] = None, + matcher: Optional["PropertyMatcher"] = None, + extension_class: Optional[Type["AbstractSyrupyExtension"]] = None, ) -> "SnapshotAssertion": """ - Creates a new snapshot assertion fixture with the same options but using - specified extension class. This does not preserve assertion index or state. + Create new snapshot assertion fixture with provided values. This preserves + provided values between assertions. """ return self.__class__( + matcher=matcher or self.matcher, + include=include or self.include, + exclude=exclude or self.exclude, update_snapshots=self.update_snapshots, test_location=self.test_location, extension_class=extension_class or self.extension_class, session=self.session, ) + def use_extension( + self, extension_class: Optional[Type["AbstractSyrupyExtension"]] = None + ) -> "SnapshotAssertion": + """ + Create new snapshot assertion fixture with the same options but using + specified extension class. This does not preserve assertion index or state. + """ + return self.with_defaults(extension_class=extension_class) + def assert_match(self, data: "SerializableData") -> None: assert self == data diff --git a/tests/examples/__snapshots__/test_custom_defaults.ambr b/tests/examples/__snapshots__/test_custom_defaults.ambr new file mode 100644 index 00000000..bf689f44 --- /dev/null +++ b/tests/examples/__snapshots__/test_custom_defaults.ambr @@ -0,0 +1,23 @@ +# serializer version: 1 +# name: test_asserting_multiple_times + dict({ + 'excluded': 'value', + 'my-field': str, + }) +# --- +# name: test_asserting_multiple_times.1 + dict({ + 'excluded': 'value', + 'my-field': int, + }) +# --- +# name: test_asserting_multiple_times_chained + dict({ + 'my-field': str, + }) +# --- +# name: test_asserting_multiple_times_chained.1 + dict({ + 'my-field': int, + }) +# --- diff --git a/tests/examples/__snapshots__/test_custom_defaults/test_asserting_multiple_times_chained_json.1.json b/tests/examples/__snapshots__/test_custom_defaults/test_asserting_multiple_times_chained_json.1.json new file mode 100644 index 00000000..39e96ded --- /dev/null +++ b/tests/examples/__snapshots__/test_custom_defaults/test_asserting_multiple_times_chained_json.1.json @@ -0,0 +1,3 @@ +{ + "my-field": "int" +} diff --git a/tests/examples/__snapshots__/test_custom_defaults/test_asserting_multiple_times_chained_json.json b/tests/examples/__snapshots__/test_custom_defaults/test_asserting_multiple_times_chained_json.json new file mode 100644 index 00000000..c55400a5 --- /dev/null +++ b/tests/examples/__snapshots__/test_custom_defaults/test_asserting_multiple_times_chained_json.json @@ -0,0 +1,3 @@ +{ + "my-field": "str" +} diff --git a/tests/examples/test_custom_defaults.py b/tests/examples/test_custom_defaults.py new file mode 100644 index 00000000..43bf8f37 --- /dev/null +++ b/tests/examples/test_custom_defaults.py @@ -0,0 +1,42 @@ +"""Example: Custom snapshot defaults + +Here we modify snapshot defaults globally, instead of per assert. +This gives the ability to modify the snapshot functionality to your hearts content +and then simply re-use them, without having to pass those defaults to every assert. +Especially useful if there's a lot of tests that need to modify the default behaviour. +""" +import pytest + +from syrupy.extensions.json import JSONSnapshotExtension +from syrupy.filters import paths +from syrupy.matchers import path_type + + +@pytest.fixture +def snapshot_matcher(snapshot): + return snapshot.with_defaults(matcher=path_type(mapping={"my-field": (str, int)})) + + +@pytest.fixture +def snapshot_exclude(snapshot_matcher): + return snapshot_matcher.with_defaults(exclude=paths("excluded")) + + +@pytest.fixture +def snapshot_json(snapshot_exclude): + return snapshot_exclude.with_defaults(extension_class=JSONSnapshotExtension) + + +def test_asserting_multiple_times(snapshot_matcher): + assert {"my-field": "my-string", "excluded": "value"} == snapshot_matcher + assert {"my-field": 12345, "excluded": "value"} == snapshot_matcher + + +def test_asserting_multiple_times_chained(snapshot_exclude): + assert {"my-field": "my-string", "excluded": "value"} == snapshot_exclude + assert {"my-field": 12345, "excluded": "value"} == snapshot_exclude + + +def test_asserting_multiple_times_chained_json(snapshot_json): + assert {"my-field": "my-string", "excluded": "value"} == snapshot_json + assert {"my-field": 12345, "excluded": "value"} == snapshot_json