Skip to content

Commit 07ea0d6

Browse files
authored
test: move and copy v2 tests into tests/legacy (#314)
1 parent f7363f3 commit 07ea0d6

File tree

11 files changed

+1401
-2
lines changed

11 files changed

+1401
-2
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ check-ci = ["check", "lint", "format-check"]
8181
test-ci = ["test-once", "coverage-xml"]
8282

8383
[tool.pytest.ini_options]
84-
addopts = "--color=yes --mypy-ini-file=tests/typing/mypy.ini --mypy-only-local-stub"
84+
addopts = "--color=yes --mypy-ini-file=tests/legacy/typing/mypy.ini --mypy-only-local-stub"
8585
asyncio_mode = "auto"
8686
filterwarnings = ["error::decoy.warnings.DecoyWarning"]
8787

tests/legacy/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Tests module for Decoy v2."""

tests/legacy/test_mock.py

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
"""Smoke and acceptance tests for main Decoy interface."""
2+
3+
import inspect
4+
import sys
5+
from typing import Any
6+
7+
import pytest
8+
9+
from decoy import Decoy, errors
10+
from decoy.spy import AsyncSpy, Spy
11+
from decoy.warnings import IncorrectCallWarning
12+
13+
from .. import fixtures
14+
15+
16+
def test_create_mock(decoy: Decoy) -> None:
17+
"""It creates a callable mock that no-ops."""
18+
subject = decoy.mock(name="alice")
19+
result = subject()
20+
21+
assert result is None
22+
assert isinstance(subject, Spy)
23+
assert repr(subject) == "<Decoy mock `alice`>"
24+
assert inspect.signature(subject) == inspect.signature(fixtures.noop)
25+
26+
27+
def test_create_mock_requires_name(decoy: Decoy) -> None:
28+
"""It requires a name for a mock with no spec."""
29+
with pytest.raises(errors.MockNameRequiredError):
30+
decoy.mock() # type:ignore[call-overload]
31+
32+
33+
def test_child_mock(decoy: Decoy) -> None:
34+
"""The attributes of a mock are also mocks."""
35+
parent = decoy.mock(name="alice")
36+
subject = parent.child
37+
result = subject()
38+
39+
assert subject is parent.child
40+
assert result is None
41+
assert isinstance(subject, Spy)
42+
assert repr(subject) == "<Decoy mock `alice.child`>"
43+
assert inspect.signature(subject) == inspect.signature(fixtures.noop)
44+
45+
46+
def test_attribute_set_and_delete(decoy: Decoy) -> None:
47+
"""The caller may set attributes on a mock and delete them to reset them."""
48+
subject = decoy.mock(name="subject")
49+
50+
subject.prop_name = 41
51+
assert subject.prop_name == 41
52+
53+
del subject.prop_name
54+
assert isinstance(subject.prop_name, Spy)
55+
56+
57+
async def test_create_async_mock(decoy: Decoy) -> None:
58+
"""It creates an async callable mock that no-ops."""
59+
subject = decoy.mock(name="alice", is_async=True)
60+
result = await subject()
61+
62+
assert result is None
63+
assert isinstance(subject, AsyncSpy)
64+
assert repr(subject) == "<Decoy mock `alice`>"
65+
assert inspect.signature(subject) == inspect.signature(fixtures.noop)
66+
67+
68+
def test_create_func_mock(decoy: Decoy) -> None:
69+
"""It creates a mock based on a function."""
70+
subject = decoy.mock(func=fixtures.some_func)
71+
result = subject("hello")
72+
73+
assert result is None
74+
assert isinstance(subject, Spy)
75+
assert repr(subject) == "<Decoy mock `tests.fixtures.some_func`>"
76+
assert inspect.signature(subject) == inspect.signature(fixtures.some_func)
77+
78+
79+
async def test_create_async_func_mock(decoy: Decoy) -> None:
80+
"""It creates a mock based on an async function."""
81+
subject = decoy.mock(func=fixtures.some_async_func)
82+
result = await subject("hello")
83+
84+
assert result is None
85+
assert isinstance(subject, AsyncSpy)
86+
assert repr(subject) == "<Decoy mock `tests.fixtures.some_async_func`>"
87+
assert inspect.signature(subject) == inspect.signature(fixtures.some_async_func)
88+
89+
90+
def test_create_decorated_func_mock(decoy: Decoy) -> None:
91+
"""It creates a mock based on a decorated function."""
92+
subject = decoy.mock(func=fixtures.some_wrapped_func)
93+
result = subject("hello")
94+
95+
assert result is None
96+
assert isinstance(subject, Spy)
97+
assert repr(subject) == "<Decoy mock `tests.fixtures.some_wrapped_func`>"
98+
assert inspect.signature(subject) == inspect.signature(fixtures.some_func)
99+
100+
101+
def test_create_callable_class_mock(decoy: Decoy) -> None:
102+
"""It creates a mock based on a callable class."""
103+
subject = decoy.mock(cls=fixtures.SomeCallableClass)
104+
result = subject(123)
105+
106+
def _expected_signature(val: int) -> int:
107+
raise NotImplementedError()
108+
109+
assert result is None
110+
assert isinstance(subject, fixtures.SomeCallableClass)
111+
assert isinstance(subject, Spy)
112+
assert repr(subject) == "<Decoy mock `tests.fixtures.SomeCallableClass`>"
113+
assert inspect.signature(subject) == inspect.signature(_expected_signature)
114+
115+
116+
async def test_create_async_callable_class_mock(decoy: Decoy) -> None:
117+
"""It creates a mock based on an async callable class."""
118+
subject = decoy.mock(cls=fixtures.SomeAsyncCallableClass)
119+
result = await subject(123)
120+
121+
def _expected_signature(val: int) -> int:
122+
raise NotImplementedError()
123+
124+
assert result is None
125+
assert isinstance(subject, fixtures.SomeAsyncCallableClass)
126+
assert isinstance(subject, AsyncSpy)
127+
assert repr(subject) == "<Decoy mock `tests.fixtures.SomeAsyncCallableClass`>"
128+
assert inspect.signature(subject) == inspect.signature(_expected_signature)
129+
130+
131+
def test_create_class_mock(decoy: Decoy) -> None:
132+
"""It creates a mock from a class spec."""
133+
subject = decoy.mock(cls=fixtures.SomeClass)
134+
135+
assert isinstance(subject, fixtures.SomeClass)
136+
assert isinstance(subject, Spy)
137+
assert repr(subject) == "<Decoy mock `tests.fixtures.SomeClass`>"
138+
139+
140+
def test_create_generic_class_mock(decoy: Decoy) -> None:
141+
"""It creates a mock from a generic class spec."""
142+
subject: fixtures.GenericClass[object] = decoy.mock(cls=fixtures.GenericClass)
143+
144+
assert isinstance(subject, fixtures.GenericClass)
145+
assert isinstance(subject, Spy)
146+
assert repr(subject) == "<Decoy mock `tests.fixtures.GenericClass`>"
147+
148+
149+
def test_create_concrete_generic_class_mock(decoy: Decoy) -> None:
150+
"""It creates a mock from an alias to a concrete generic class spec."""
151+
subject = decoy.mock(cls=fixtures.ConcreteAlias)
152+
153+
assert isinstance(subject, fixtures.GenericClass)
154+
assert isinstance(subject, Spy)
155+
assert repr(subject) == "<Decoy mock `tests.fixtures.GenericClass`>"
156+
157+
158+
def test_create_method_mock(decoy: Decoy) -> None:
159+
"""It creates a child mock from a class spec."""
160+
subject = decoy.mock(cls=fixtures.SomeClass).foo
161+
result = subject("hello")
162+
163+
def _expected_signature(val: str) -> str:
164+
raise NotImplementedError()
165+
166+
assert result is None
167+
assert isinstance(subject, Spy)
168+
assert inspect.signature(subject) == inspect.signature(_expected_signature)
169+
assert repr(subject) == "<Decoy mock `tests.fixtures.SomeClass.foo`>"
170+
171+
172+
def test_create_staticmethod_mock(decoy: Decoy) -> None:
173+
"""It creates a child staticmethod mock from a class spec."""
174+
subject = decoy.mock(cls=fixtures.SomeClass).static_method
175+
result = subject("hello")
176+
177+
def _expected_signature(hello: str) -> int:
178+
raise NotImplementedError()
179+
180+
assert result is None
181+
assert isinstance(subject, Spy)
182+
assert inspect.signature(subject) == inspect.signature(_expected_signature)
183+
assert repr(subject) == "<Decoy mock `tests.fixtures.SomeClass.static_method`>"
184+
185+
186+
def test_create_classmethod_mock(decoy: Decoy) -> None:
187+
"""It creates a child classmethod mock from a class spec."""
188+
subject = decoy.mock(cls=fixtures.SomeClass).class_method
189+
result = subject("hello")
190+
191+
def _expected_signature(hello: str) -> int:
192+
raise NotImplementedError()
193+
194+
assert result is None
195+
assert isinstance(subject, Spy)
196+
assert inspect.signature(subject) == inspect.signature(_expected_signature)
197+
assert repr(subject) == "<Decoy mock `tests.fixtures.SomeClass.class_method`>"
198+
199+
200+
def test_create_decorated_method_mock(decoy: Decoy) -> None:
201+
"""It creates a child mock of a decorated method from a class spec."""
202+
subject = decoy.mock(cls=fixtures.SomeClass).some_wrapped_method
203+
result = subject("hello")
204+
205+
def _expected_signature(val: str) -> str:
206+
raise NotImplementedError()
207+
208+
assert result is None
209+
assert isinstance(subject, Spy)
210+
assert inspect.signature(subject) == inspect.signature(_expected_signature)
211+
assert (
212+
repr(subject) == "<Decoy mock `tests.fixtures.SomeClass.some_wrapped_method`>"
213+
)
214+
215+
216+
def test_create_attribute_mock(decoy: Decoy) -> None:
217+
"""It creates a child mock of a class's primitive attribute."""
218+
subject = decoy.mock(cls=fixtures.SomeClass).some_attr
219+
220+
expected_signature = (
221+
inspect.signature(bool)
222+
if sys.version_info >= (3, 13)
223+
else inspect.signature(fixtures.noop)
224+
)
225+
226+
assert isinstance(subject, Spy)
227+
assert inspect.signature(subject) == expected_signature
228+
assert repr(subject) == "<Decoy mock `tests.fixtures.SomeClass.some_attr`>"
229+
230+
231+
def test_create_attribute_class_mock(decoy: Decoy) -> None:
232+
"""It creates a child class mock from an attribute."""
233+
subject = decoy.mock(cls=fixtures.SomeNestedClass).child_attr
234+
235+
assert isinstance(subject, Spy)
236+
assert isinstance(subject, fixtures.SomeClass)
237+
238+
239+
def test_create_attribute_type_alias_class_mock(decoy: Decoy) -> None:
240+
"""It creates a child class mock from a type alias attribute."""
241+
subject = decoy.mock(cls=fixtures.SomeNestedClass).alias_attr
242+
243+
assert isinstance(subject, Spy)
244+
assert isinstance(subject, fixtures.GenericClass)
245+
246+
247+
def test_create_property_class_mock(decoy: Decoy) -> None:
248+
"""It creates a child class mock from an property getter."""
249+
subject = decoy.mock(cls=fixtures.SomeNestedClass).child
250+
251+
assert isinstance(subject, Spy)
252+
assert isinstance(subject, fixtures.SomeClass)
253+
254+
255+
def test_create_optional_property_class_mock(decoy: Decoy) -> None:
256+
"""It creates a child class mock from an property getter with Optional return."""
257+
subject = decoy.mock(cls=fixtures.SomeNestedClass).optional_child
258+
assert isinstance(subject, Spy)
259+
assert isinstance(subject, fixtures.SomeClass)
260+
261+
subject = decoy.mock(cls=fixtures.SomeNestedClass).union_child_and_none
262+
assert isinstance(subject, Spy)
263+
assert isinstance(subject, fixtures.SomeClass)
264+
265+
subject = decoy.mock(cls=fixtures.SomeNestedClass).union_none_and_child
266+
assert isinstance(subject, Spy)
267+
assert isinstance(subject, fixtures.SomeClass)
268+
269+
270+
def test_create_union_class_mock(decoy: Decoy) -> None:
271+
"""A child class mock from an property with union return is not typed."""
272+
subject = decoy.mock(cls=fixtures.SomeNestedClass).union_child
273+
274+
assert isinstance(subject, Spy)
275+
assert not isinstance(subject, fixtures.SomeClass)
276+
assert not isinstance(subject, fixtures.SomeAsyncClass)
277+
278+
279+
def test_create_type_alias_class_mock(decoy: Decoy) -> None:
280+
"""It creates a child class mock from an property getter with type alias return."""
281+
subject = decoy.mock(cls=fixtures.SomeNestedClass).alias_child
282+
283+
assert isinstance(subject, Spy)
284+
assert isinstance(subject, fixtures.GenericClass)
285+
286+
287+
def test_create_untyped_property_mock(decoy: Decoy) -> None:
288+
"""It creates a child mock of a class's property getter without a type."""
289+
subject = decoy.mock(cls=fixtures.SomeClass).mystery_property
290+
291+
assert isinstance(subject, Spy)
292+
assert inspect.signature(subject) == inspect.signature(fixtures.noop)
293+
assert repr(subject) == "<Decoy mock `tests.fixtures.SomeClass.mystery_property`>"
294+
295+
296+
def test_func_bad_call(decoy: Decoy) -> None:
297+
"""It raises an IncorrectCallWarning if call is bad."""
298+
subject = decoy.mock(func=fixtures.some_func)
299+
300+
with pytest.warns(IncorrectCallWarning):
301+
subject("hello", "world") # type: ignore[call-arg]
302+
303+
304+
@pytest.mark.filterwarnings("ignore:'NoneType' object is not subscriptable")
305+
def test_bad_type_hints(decoy: Decoy) -> None:
306+
"""It tolerates bad type hints without failing at runtime."""
307+
308+
class _BadTypeHints:
309+
not_ok: "None[Any]" # pyright: ignore[reportInvalidTypeArguments]
310+
311+
subject = decoy.mock(cls=_BadTypeHints).not_ok
312+
313+
assert isinstance(subject, Spy)
314+
315+
316+
def test_context_manager(decoy: Decoy) -> None:
317+
"""It creates a context manager."""
318+
subject = decoy.mock(name="cm")
319+
320+
with subject as result:
321+
assert result is None
322+
323+
324+
async def test_async_context_manager(decoy: Decoy) -> None:
325+
"""It creates an async context manager."""
326+
subject = decoy.mock(name="acm")
327+
328+
async with subject as result:
329+
assert result is None
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from decoy import Decoy
66
from decoy.errors import MissingRehearsalError
77

8-
from .fixtures import SomeClass
8+
from ..fixtures import SomeClass
99

1010

1111
def test_property_missing_rehearsal(decoy: Decoy) -> None:

tests/legacy/test_unittest.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Simple test suite to ensure Decoy works with unittest."""
2+
3+
import unittest
4+
5+
from decoy import Decoy
6+
7+
from ..fixtures import SomeClass
8+
9+
10+
class DecoyTestCase(unittest.TestCase):
11+
"""Decoy test case using unittest."""
12+
13+
def setUp(self) -> None:
14+
"""Set up before each test."""
15+
self.decoy = Decoy()
16+
17+
def tearDown(self) -> None:
18+
"""Clean up after each test."""
19+
self.decoy.reset()
20+
21+
def test_when(self) -> None:
22+
"""Test that self.decoy.when works."""
23+
mock = self.decoy.mock(cls=SomeClass)
24+
self.decoy.when(mock.foo("hello")).then_return("world")
25+
assert mock.foo("hello") == "world"
26+
27+
def test_verify(self) -> None:
28+
"""Test that self.decoy.verify works."""
29+
mock = self.decoy.mock(cls=SomeClass)
30+
mock.foo("hello")
31+
self.decoy.verify(mock.foo("hello"))

0 commit comments

Comments
 (0)