Skip to content

Commit

Permalink
feat: support setting defaults (#802)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaljelonek committed Sep 1, 2023
1 parent 11b0722 commit c877559
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 5 deletions.
15 changes: 14 additions & 1 deletion README.md
Expand Up @@ -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:
Expand All @@ -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):
Expand Down Expand Up @@ -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)
Expand Down
31 changes: 27 additions & 4 deletions src/syrupy/assertion.py
Expand Up @@ -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,
Expand Down Expand Up @@ -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"]
Expand Down Expand Up @@ -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

Expand Down
23 changes: 23 additions & 0 deletions 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,
})
# ---
@@ -0,0 +1,3 @@
{
"my-field": "int"
}
@@ -0,0 +1,3 @@
{
"my-field": "str"
}
42 changes: 42 additions & 0 deletions 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

0 comments on commit c877559

Please sign in to comment.