Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support setting defaults #802

Merged
merged 1 commit into from
Sep 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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,
})
# ---
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"my-field": "int"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"my-field": "str"
}
42 changes: 42 additions & 0 deletions tests/examples/test_custom_defaults.py
Original file line number Diff line number Diff line change
@@ -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