Skip to content

Commit 0f76ec0

Browse files
committed
feat(test-snapshot): add prefix to snapshot fixture
1 parent c0f0a91 commit 0f76ec0

File tree

12 files changed

+176
-14
lines changed

12 files changed

+176
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- refactor(core): use `str_to_bool` of `python-strtobool` instead of `strtobool`
66
of `distutils`
7+
- feat(test-snapshot): add prefix to snapshot fixture
78

89
## Version 0.15.8
910

redux_pytest/fixtures/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from .event_loop import LoopThread, event_loop # noqa: E402
1414
from .monitor import StoreMonitor, store_monitor # noqa: E402
15-
from .snapshot import StoreSnapshot, store_snapshot # noqa: E402
15+
from .snapshot import StoreSnapshot, snapshot_prefix, store_snapshot # noqa: E402
1616
from .store import needs_finish, store # noqa: E402
1717
from .wait_for import Waiter, WaitFor, wait_for # noqa: E402
1818

@@ -24,6 +24,7 @@
2424
'WaitFor',
2525
'event_loop',
2626
'needs_finish',
27+
'snapshot_prefix',
2728
'store',
2829
'store_monitor',
2930
'store_snapshot',

redux_pytest/fixtures/snapshot.py

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
import json
77
import os
88
from collections import defaultdict
9-
from distutils.util import strtobool
109
from pathlib import Path
1110
from typing import TYPE_CHECKING, Any, Callable, Generic, cast
1211

1312
import pytest
13+
from str_to_bool import str_to_bool
1414

1515
from redux.basic_types import FinishEvent, State
1616

@@ -23,15 +23,17 @@
2323
class StoreSnapshot(Generic[State]):
2424
"""Context object for tests taking snapshots of the store."""
2525

26-
def __init__(
26+
def __init__( # noqa: PLR0913
2727
self: StoreSnapshot,
2828
*,
2929
test_id: str,
3030
path: Path,
3131
override: bool,
3232
store: Store,
33+
prefix: str | None,
3334
) -> None:
3435
"""Create a new store snapshot context."""
36+
self.prefix = prefix
3537
self._is_failed = False
3638
self._is_closed = False
3739
self.override = override
@@ -41,8 +43,13 @@ def __init__(
4143
path.parent / 'results' / file / test_id.split('::')[-1][5:],
4244
)
4345
if self.results_dir.exists():
46+
prefix_element = ''
47+
if self.prefix:
48+
prefix_element = self.prefix + '-'
4449
for file in self.results_dir.glob(
45-
'store-*.jsonc' if override else 'store-*.mismatch.jsonc',
50+
f'store-{prefix_element}*.jsonc'
51+
if override
52+
else f'store-{prefix_element}*.mismatch.jsonc',
4653
):
4754
file.unlink() # pragma: no cover
4855
self.results_dir.mkdir(parents=True, exist_ok=True)
@@ -69,9 +76,15 @@ def json_snapshot(
6976

7077
def get_filename(self: StoreSnapshot[State], title: str | None) -> str:
7178
"""Get the filename for the snapshot."""
79+
title_element = ''
7280
if title:
73-
return f"""store-{title}-{self.test_counter[title]:03d}"""
74-
return f"""store-{self.test_counter[title]:03d}"""
81+
title_element = title + '-'
82+
prefix_element = ''
83+
if self.prefix:
84+
prefix_element = self.prefix + '-'
85+
return (
86+
f"""store-{prefix_element}{title_element}{self.test_counter[title]:03d}"""
87+
)
7588

7689
def take(
7790
self: StoreSnapshot[State],
@@ -101,10 +114,10 @@ def take(
101114
if json_path.exists():
102115
old_snapshot = json_path.read_text().split('\n', 1)[1][:-1]
103116
else:
104-
old_snapshot = None
105-
if old_snapshot != new_snapshot:
117+
old_snapshot = None # pragma: no cover
118+
if old_snapshot != new_snapshot: # pragma: no cover
106119
self._is_failed = True
107-
mismatch_path.write_text( # pragma: no cover
120+
mismatch_path.write_text(
108121
f'// MISMATCH: {filename}\n{new_snapshot}\n',
109122
)
110123
assert new_snapshot == old_snapshot, f'Store snapshot mismatch - {filename}'
@@ -123,7 +136,7 @@ def _(state: object | None) -> None:
123136
def close(self: StoreSnapshot[State]) -> None:
124137
"""Close the snapshot context."""
125138
self._is_closed = True
126-
if self._is_failed:
139+
if self._is_failed: # pragma: no cover
127140
return
128141
for title in self.test_counter:
129142
filename = self.get_filename(title)
@@ -133,14 +146,24 @@ def close(self: StoreSnapshot[State]) -> None:
133146

134147

135148
@pytest.fixture()
136-
def store_snapshot(request: SubRequest, store: Store) -> StoreSnapshot:
149+
def snapshot_prefix() -> str | None:
150+
"""Return the prefix for the snapshots."""
151+
return None
152+
153+
154+
@pytest.fixture()
155+
def store_snapshot(
156+
request: SubRequest,
157+
store: Store,
158+
snapshot_prefix: str | None,
159+
) -> StoreSnapshot:
137160
"""Take a snapshot of the current state of the store."""
138161
override = (
139162
request.config.getoption(
140163
'--override-store-snapshots',
141164
default=cast(
142165
Any,
143-
strtobool(os.environ.get('REDUX_TEST_OVERRIDE_SNAPSHOTS', 'false'))
166+
str_to_bool(os.environ.get('REDUX_TEST_OVERRIDE_SNAPSHOTS', 'false'))
144167
== 1,
145168
),
146169
)
@@ -151,4 +174,5 @@ def store_snapshot(request: SubRequest, store: Store) -> StoreSnapshot:
151174
path=request.node.path,
152175
override=override,
153176
store=store,
177+
prefix=snapshot_prefix,
154178
)

tests/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from redux_pytest.fixtures import (
1313
event_loop,
1414
needs_finish,
15+
snapshot_prefix,
1516
store,
1617
store_monitor,
1718
store_snapshot,
@@ -21,6 +22,7 @@
2122
__all__ = [
2223
'event_loop',
2324
'needs_finish',
25+
'snapshot_prefix',
2426
'store',
2527
'store_monitor',
2628
'store_snapshot',
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// store-000
2+
1
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// store-001
2+
3
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// store-custom_prefix-000
2+
0
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// store-custom_prefix-001
2+
1

tests/test_monitor_fixtures.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ class DummyEvent(BaseEvent): ...
3939
Action = IncrementAction | InitAction | FinishAction
4040

4141

42+
@pytest.fixture()
43+
def snapshot_prefix() -> str:
44+
return 'prefix'
45+
46+
4247
def reducer(
4348
state: StateType | None,
4449
action: Action,

tests/test_performance.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def store() -> Generator[StoreType, None, None]:
6464

6565

6666
def test_simple_dispatch(store: StoreType) -> None:
67-
count = 100000
67+
count = 50000
6868
for _ in range(count):
6969
store.dispatch(IncrementAction())
7070

@@ -80,7 +80,7 @@ def callback(_: StateType | None) -> None:
8080

8181
store.subscribe(callback)
8282

83-
count = 500
83+
count = 400
8484
for _ in range(count):
8585
store.dispatch(IncrementAction())
8686

tests/test_snapshot.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# ruff: noqa: D100, D101, D102, D103, D104, D107
2+
from __future__ import annotations
3+
4+
from immutable import Immutable
5+
6+
from redux.basic_types import (
7+
BaseAction,
8+
CreateStoreOptions,
9+
FinishAction,
10+
FinishEvent,
11+
)
12+
from redux.main import Store
13+
14+
15+
class StateType(Immutable):
16+
value: int
17+
18+
19+
StoreType = Store[StateType, BaseAction, FinishEvent]
20+
21+
22+
def test_snapshot() -> None:
23+
initial_state = StateType(value=0)
24+
25+
store = Store(
26+
lambda state, __: state or initial_state,
27+
options=CreateStoreOptions(auto_init=True),
28+
)
29+
30+
assert store.snapshot == {'value': 0}
31+
32+
store.dispatch(FinishAction())

tests/test_snapshot_fixture.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# ruff: noqa: D100, D101, D102, D103, D104, D107
2+
from __future__ import annotations
3+
4+
from dataclasses import replace
5+
from typing import TYPE_CHECKING
6+
7+
import pytest
8+
from immutable import Immutable
9+
10+
from redux.basic_types import (
11+
BaseAction,
12+
BaseEvent,
13+
CompleteReducerResult,
14+
CreateStoreOptions,
15+
FinishAction,
16+
FinishEvent,
17+
InitAction,
18+
InitializationActionError,
19+
)
20+
from redux.main import Store
21+
22+
if TYPE_CHECKING:
23+
from redux_pytest.fixtures.snapshot import StoreSnapshot
24+
25+
26+
class StateType(Immutable):
27+
value: int
28+
29+
30+
class IncrementAction(BaseAction): ...
31+
32+
33+
class DummyEvent(BaseEvent): ...
34+
35+
36+
Action = IncrementAction | InitAction | FinishAction
37+
38+
39+
def reducer(
40+
state: StateType | None,
41+
action: Action,
42+
) -> StateType | CompleteReducerResult[StateType, Action, DummyEvent | FinishEvent]:
43+
if state is None:
44+
if isinstance(action, InitAction):
45+
return StateType(value=0)
46+
raise InitializationActionError(action)
47+
48+
if isinstance(action, IncrementAction):
49+
return replace(state, value=state.value + 1)
50+
return state
51+
52+
53+
@pytest.fixture()
54+
def store() -> Store:
55+
return Store(
56+
reducer,
57+
options=CreateStoreOptions(
58+
auto_init=True,
59+
),
60+
)
61+
62+
63+
def test_monitor(
64+
store: Store,
65+
store_snapshot: StoreSnapshot[StateType],
66+
needs_finish: None,
67+
) -> None:
68+
_ = needs_finish
69+
store_snapshot.monitor(lambda state: state.value if state.value % 2 != 0 else None)
70+
store.dispatch(IncrementAction())
71+
store.dispatch(IncrementAction())
72+
store.dispatch(IncrementAction())
73+
store.dispatch(IncrementAction())
74+
75+
76+
class TestSnapshotPrefix:
77+
@pytest.fixture(scope='class')
78+
def snapshot_prefix(self: TestSnapshotPrefix) -> str:
79+
return 'custom_prefix'
80+
81+
def test_prefix(
82+
self: TestSnapshotPrefix,
83+
store: Store,
84+
store_snapshot: StoreSnapshot[StateType],
85+
needs_finish: None,
86+
) -> None:
87+
_ = needs_finish
88+
store_snapshot.monitor(lambda state: state.value)
89+
store.dispatch(IncrementAction())

0 commit comments

Comments
 (0)