Skip to content

Commit 4b3100f

Browse files
authored
test: remove and replace redundant core tests (#308)
1 parent b766bf9 commit 4b3100f

File tree

3 files changed

+205
-497
lines changed

3 files changed

+205
-497
lines changed

tests/fixtures.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
class SomeClass:
88
"""Testing class."""
99

10+
some_attr: bool
11+
1012
def foo(self, val: str) -> str:
1113
"""Get the foo string."""
1214
raise NotImplementedError()
@@ -16,7 +18,12 @@ def bar(self, a: int, b: float, c: str) -> bool:
1618
raise NotImplementedError()
1719

1820
@staticmethod
19-
def fizzbuzz(hello: str) -> int:
21+
def static_method(hello: str) -> int:
22+
"""Fizz some buzzes."""
23+
raise NotImplementedError()
24+
25+
@classmethod
26+
def class_method(cls, hello: str) -> int:
2027
"""Fizz some buzzes."""
2128
raise NotImplementedError()
2229

@@ -60,7 +67,12 @@ def optional_child(self) -> Optional[SomeClass]:
6067
raise NotImplementedError()
6168

6269
@property
63-
def union_none_child(self) -> Union[None, SomeClass]:
70+
def union_none_and_child(self) -> Union[None, SomeClass]:
71+
"""Get the child instance."""
72+
raise NotImplementedError()
73+
74+
@property
75+
def union_child_and_none(self) -> Union[SomeClass, None]:
6476
"""Get the child instance."""
6577
raise NotImplementedError()
6678

@@ -105,9 +117,9 @@ async def __call__(self, val: int) -> int:
105117

106118

107119
class SomeCallableClass:
108-
"""Async callable class."""
120+
"""Callable class."""
109121

110-
async def __call__(self, val: int) -> int:
122+
def __call__(self, val: int) -> int:
111123
"""Get an integer."""
112124
raise NotImplementedError()
113125

tests/test_mock.py

Lines changed: 189 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,48 @@
11
"""Smoke and acceptance tests for main Decoy interface."""
22

33
import inspect
4+
import sys
5+
from typing import Any
46

57
import pytest
68

79
from decoy import Decoy, errors
810
from decoy.spy import AsyncSpy, Spy
911
from decoy.warnings import IncorrectCallWarning
1012

11-
from .fixtures import SomeClass, some_func
13+
from .fixtures import (
14+
SomeAsyncCallableClass,
15+
SomeAsyncClass,
16+
SomeCallableClass,
17+
SomeClass,
18+
SomeNestedClass,
19+
noop,
20+
some_async_func,
21+
some_func,
22+
)
1223

1324

1425
def test_create_mock(decoy: Decoy) -> None:
15-
"""It creates a mock from a class."""
26+
"""It creates a mock from a class spec."""
1627
subject = decoy.mock(cls=SomeClass)
1728

1829
assert isinstance(subject, SomeClass)
1930
assert isinstance(subject, Spy)
2031
assert repr(subject) == "<Decoy mock `tests.fixtures.SomeClass`>"
2132

2233

34+
def test_create_method_mock(decoy: Decoy) -> None:
35+
"""It creates a child mock from a class spec."""
36+
subject = decoy.mock(cls=SomeClass).foo
37+
38+
def _expected_signature(val: str) -> str:
39+
raise NotImplementedError()
40+
41+
assert isinstance(subject, Spy)
42+
assert inspect.signature(subject) == inspect.signature(_expected_signature)
43+
assert repr(subject) == "<Decoy mock `tests.fixtures.SomeClass.foo`>"
44+
45+
2346
def test_method_noop(decoy: Decoy) -> None:
2447
"""A method mock no-ops by default."""
2548
subject = decoy.mock(cls=SomeClass)
@@ -28,8 +51,126 @@ def test_method_noop(decoy: Decoy) -> None:
2851
assert result is None
2952

3053

31-
def test_decoy_creates_func_spy(decoy: Decoy) -> None:
32-
"""It should be able to create a Spy from a function."""
54+
def test_create_staticmethod_mock(decoy: Decoy) -> None:
55+
"""It creates a child staticmethod mock from a class spec."""
56+
subject = decoy.mock(cls=SomeClass).static_method
57+
58+
def _expected_signature(hello: str) -> int:
59+
raise NotImplementedError()
60+
61+
assert isinstance(subject, Spy)
62+
assert inspect.signature(subject) == inspect.signature(_expected_signature)
63+
assert repr(subject) == "<Decoy mock `tests.fixtures.SomeClass.static_method`>"
64+
65+
66+
def test_create_classmethod_mock(decoy: Decoy) -> None:
67+
"""It creates a child classmethod mock from a class spec."""
68+
subject = decoy.mock(cls=SomeClass).class_method
69+
70+
def _expected_signature(hello: str) -> int:
71+
raise NotImplementedError()
72+
73+
assert isinstance(subject, Spy)
74+
assert inspect.signature(subject) == inspect.signature(_expected_signature)
75+
assert repr(subject) == "<Decoy mock `tests.fixtures.SomeClass.class_method`>"
76+
77+
78+
def test_create_decorated_method_mock(decoy: Decoy) -> None:
79+
"""It creates a child mock of a decorated method from a class spec."""
80+
subject = decoy.mock(cls=SomeClass).some_wrapped_method
81+
82+
def _expected_signature(val: str) -> str:
83+
raise NotImplementedError()
84+
85+
assert isinstance(subject, Spy)
86+
assert inspect.signature(subject) == inspect.signature(_expected_signature)
87+
assert (
88+
repr(subject) == "<Decoy mock `tests.fixtures.SomeClass.some_wrapped_method`>"
89+
)
90+
91+
92+
def test_create_attribute_mock(decoy: Decoy) -> None:
93+
"""It creates a child mock of an attribute."""
94+
subject = decoy.mock(cls=SomeClass).some_attr
95+
96+
expected_signature = (
97+
inspect.signature(bool)
98+
if sys.version_info >= (3, 13)
99+
else inspect.signature(noop)
100+
)
101+
102+
assert isinstance(subject, Spy)
103+
assert inspect.signature(subject) == expected_signature
104+
assert repr(subject) == "<Decoy mock `tests.fixtures.SomeClass.some_attr`>"
105+
106+
107+
def test_create_attribute_class_mock(decoy: Decoy) -> None:
108+
"""It creates a child class mock from an attribute."""
109+
subject = decoy.mock(cls=SomeNestedClass).child_attr
110+
111+
assert isinstance(subject, Spy)
112+
assert isinstance(subject, SomeClass)
113+
114+
115+
def test_create_property_class_mock(decoy: Decoy) -> None:
116+
"""It creates a child class mock from an property getter."""
117+
subject = decoy.mock(cls=SomeNestedClass).child
118+
119+
assert isinstance(subject, Spy)
120+
assert isinstance(subject, SomeClass)
121+
122+
123+
def test_create_optional_property_class_mock(decoy: Decoy) -> None:
124+
"""It creates a child class mock from an property getter with Optional return."""
125+
subject = decoy.mock(cls=SomeNestedClass).optional_child
126+
assert isinstance(subject, Spy)
127+
assert isinstance(subject, SomeClass)
128+
129+
subject = decoy.mock(cls=SomeNestedClass).union_child_and_none
130+
assert isinstance(subject, Spy)
131+
assert isinstance(subject, SomeClass)
132+
133+
subject = decoy.mock(cls=SomeNestedClass).union_none_and_child
134+
assert isinstance(subject, Spy)
135+
assert isinstance(subject, SomeClass)
136+
137+
138+
def test_create_union_class_mock(decoy: Decoy) -> None:
139+
"""A child class mock from an property with union return is not typed."""
140+
subject = decoy.mock(cls=SomeNestedClass).union_child
141+
142+
assert isinstance(subject, Spy)
143+
assert not isinstance(subject, SomeClass)
144+
assert not isinstance(subject, SomeAsyncClass)
145+
146+
147+
def test_create_callable_mock(decoy: Decoy) -> None:
148+
"""It creates a mock from a callable class."""
149+
subject = decoy.mock(cls=SomeCallableClass)
150+
151+
def _expected_signature(val: int) -> int:
152+
raise NotImplementedError()
153+
154+
assert isinstance(subject, Spy)
155+
assert isinstance(subject, SomeCallableClass)
156+
assert inspect.signature(subject) == inspect.signature(_expected_signature)
157+
158+
159+
def test_create_async_callable_mock(decoy: Decoy) -> None:
160+
"""It creates a mock from an async callable class."""
161+
subject = decoy.mock(cls=SomeAsyncCallableClass)
162+
163+
async def _expected_signature(val: int) -> int:
164+
raise NotImplementedError()
165+
166+
assert isinstance(subject, AsyncSpy)
167+
assert isinstance(subject, SomeAsyncCallableClass)
168+
assert inspect.signature(subject) == inspect.signature(_expected_signature)
169+
assert inspect.iscoroutinefunction(subject.__call__)
170+
171+
172+
def test_create_func_mock(decoy: Decoy) -> None:
173+
"""It creates a mock from a function spec."""
33174
subject = decoy.mock(func=some_func)
34175

35176
assert isinstance(subject, Spy)
@@ -45,6 +186,23 @@ def test_func_noop(decoy: Decoy) -> None:
45186
assert result is None
46187

47188

189+
def test_create_async_func_mock(decoy: Decoy) -> None:
190+
"""It creates a mock from an async function spec."""
191+
subject = decoy.mock(func=some_async_func)
192+
193+
assert isinstance(subject, AsyncSpy)
194+
assert inspect.signature(subject) == inspect.signature(some_async_func)
195+
assert repr(subject) == "<Decoy mock `tests.fixtures.some_async_func`>"
196+
197+
198+
async def test_async_func_noop(decoy: Decoy) -> None:
199+
"""An async function mock no-ops by default."""
200+
subject = decoy.mock(func=some_async_func)
201+
result = await subject("hello")
202+
203+
assert result is None
204+
205+
48206
def test_func_bad_call(decoy: Decoy) -> None:
49207
"""It raises an IncorrectCallWarning if call is bad."""
50208
subject = decoy.mock(func=some_func)
@@ -53,22 +211,43 @@ def test_func_bad_call(decoy: Decoy) -> None:
53211
subject("hello", "world") # type: ignore[call-arg]
54212

55213

56-
def test_decoy_creates_specless_spy(decoy: Decoy) -> None:
57-
"""It should be able to create a spec-less spy."""
214+
def test_create_specless_mock(decoy: Decoy) -> None:
215+
"""It creates a mock without a spec."""
58216
subject = decoy.mock(name="subject")
59217

60218
assert isinstance(subject, Spy)
61219
assert repr(subject) == "<Decoy mock `subject`>"
62220

63221

64-
def test_decoy_creates_specless_async_spy(decoy: Decoy) -> None:
65-
"""It should be able to create an async specless spy."""
222+
def test_create_specless_async_mock(decoy: Decoy) -> None:
223+
"""It creates an async mock without a spec."""
66224
subject = decoy.mock(name="subject", is_async=True)
67225

68226
assert isinstance(subject, AsyncSpy)
227+
assert inspect.iscoroutinefunction(subject.__call__)
69228

70229

71-
def test_decoy_mock_name_required(decoy: Decoy) -> None:
72-
"""A name should be required for the mock."""
230+
async def test_async_specless_mock_noop(decoy: Decoy) -> None:
231+
"""An async specless mock no-ops by default."""
232+
subject = decoy.mock(name="subject", is_async=True)
233+
result = await subject("hello")
234+
235+
assert result is None
236+
237+
238+
def test_mock_name_required(decoy: Decoy) -> None:
239+
"""A name is required for a mock without a spec."""
73240
with pytest.raises(errors.MockNameRequiredError):
74241
decoy.mock() # type: ignore[call-overload]
242+
243+
244+
@pytest.mark.filterwarnings("ignore:'NoneType' object is not subscriptable")
245+
def test_bad_type_hints(decoy: Decoy) -> None:
246+
"""It tolerates bad type hints without failing at runtime."""
247+
248+
class _BadTypeHints:
249+
not_ok: "None[Any]" # pyright: ignore[reportInvalidTypeArguments]
250+
251+
subject = decoy.mock(cls=_BadTypeHints).not_ok
252+
253+
assert isinstance(subject, Spy)

0 commit comments

Comments
 (0)