Skip to content

Commit 576c3cd

Browse files
authored
Merge pull request #1 from ntoll/codespace-improved-disco-gpj5pp6jh9qv9
Pending changes exported from your codespace
2 parents 34903b9 + c9d592c commit 576c3cd

File tree

4 files changed

+185
-42
lines changed

4 files changed

+185
-42
lines changed

tests/test_asyncmock.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""
22
Ensure AsyncMock works as expected.
3-
"""
3+
"""

tests/test_mock.py

Lines changed: 145 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,161 @@
33
exacty the same as unittest.mock.Mock).
44
"""
55

6+
import upytest
67
from umock import Mock
78

89

910
def test_init_mock():
1011
"""
11-
A Mock object should be created with no attributes.
12+
A plain Mock object can be created with no arguments. Accessing arbitrary
13+
attributes on such an object (without a spec) should return another Mock
14+
object.
1215
"""
1316
mock = Mock()
14-
assert mock.__dict__ == {}, "Not an empty dict."
17+
assert mock.call_count == 0, "Non zero call count with new Mock object."
18+
assert isinstance(
19+
mock.foo, Mock
20+
), "Attribute access did not return a Mock."
1521

16-
def test_init_mock_with_spec():
22+
23+
def test_init_mock_with_spec_from_list():
24+
"""
25+
A Mock object should be created with the specified list of attributes.
26+
Accessing arbitrary attributes not in the list should raise an
27+
AttributeError.
28+
29+
If an arbitrary attribute is subqeuently added to the mock object, it
30+
should be accessible as per normal Python behaviour.
31+
"""
32+
mock = Mock(spec=["foo", "bar"])
33+
assert hasattr(mock, "foo"), "Mock object missing 'foo' attribute."
34+
assert hasattr(mock, "bar"), "Mock object missing 'bar' attribute."
35+
assert not hasattr(
36+
mock, "baz"
37+
), "Mock object has unexpected 'baz' attribute."
38+
mock.baz = "test"
39+
assert mock.baz == "test", "Mock object attribute 'baz' not set correctly."
40+
41+
42+
def test_init_mock_with_spec_from_object():
43+
"""
44+
A Mock object should be created with the specified attributes derived from
45+
the referenced instance. The Mock's __class__ should be set to that of the
46+
spec object's. Accessing arbitrary attributes not on the class should raise
47+
an AttributeError.
48+
49+
If an arbitrary attribute is subqeuently added to the mock object, it
50+
should be accessible as per normal Python behaviour.
51+
"""
52+
53+
class TestClass:
54+
x = 1
55+
y = 2
56+
57+
obj = TestClass()
58+
mock = Mock(spec=obj)
59+
assert hasattr(mock, "x"), "Mock object missing 'x' attribute."
60+
assert hasattr(mock, "y"), "Mock object missing 'y' attribute."
61+
assert not hasattr(mock, "z"), "Mock object has unexpected 'z' attribute."
62+
assert mock.__class__ == TestClass, "Mock object has unexpected class."
63+
mock.z = "test"
64+
assert mock.z == "test", "Mock object attribute 'z' not set correctly."
65+
66+
67+
def test_init_mock_with_spec_from_class():
68+
"""
69+
A Mock object should be created with the specified attributes derived from
70+
the referenced class. Since this is a class spec, the Mock's __class__
71+
remains as Mock. Accessing arbitrary attributes not on the class should
72+
raise an AttributeError.
73+
74+
If an arbitrary attribute is subqeuently added to the mock object, it
75+
should be accessible as per normal Python behaviour.
76+
"""
77+
78+
class TestClass:
79+
x = 1
80+
y = 2
81+
82+
mock = Mock(spec=TestClass)
83+
assert hasattr(mock, "x"), "Mock object missing 'x' attribute."
84+
assert hasattr(mock, "y"), "Mock object missing 'y' attribute."
85+
assert not hasattr(mock, "z"), "Mock object has unexpected 'z' attribute."
86+
assert mock.__class__ == Mock, "Mock object has unexpected class."
87+
mock.z = "test"
88+
assert mock.z == "test", "Mock object attribute 'z' not set correctly."
89+
90+
91+
def test_init_mock_with_callable_side_effect():
92+
"""
93+
A Mock object should be created with the specified callable side effect
94+
that computes the result of a call on the mock object.
95+
"""
96+
97+
def side_effect(a, b):
98+
return a + b
99+
100+
mock = Mock(side_effect=side_effect)
101+
assert (
102+
mock(1, 2) == 3
103+
), "Mock object side effect did not compute correctly."
104+
105+
106+
def test_init_mock_with_exception_class_side_effect():
107+
"""
108+
A Mock object should be created with the specified exception class side
109+
effect that raises the exception when the mock object is called.
110+
"""
111+
112+
class TestException(Exception):
113+
pass
114+
115+
mock = Mock(side_effect=TestException)
116+
with upytest.raises(TestException):
117+
mock()
118+
119+
120+
def test_init_mock_with_exception_instance_side_effect():
121+
"""
122+
A Mock object should be created with the specified exception instance side
123+
effect that is raised when the mock object is called.
124+
"""
125+
126+
ex = ValueError("test")
127+
mock = Mock(side_effect=ex)
128+
with upytest.raises(ValueError) as expected:
129+
mock()
130+
assert (
131+
str(expected.exception.value) == "test"
132+
), "Exception message not as expected."
133+
134+
135+
def test_init_mock_with_iterable_side_effect():
136+
"""
137+
A Mock object should be created with the specified iterable side effect
138+
that returns the next item in the iterable each time the mock object is
139+
called.
140+
"""
141+
142+
mock = Mock(side_effect=[1, 2, 3])
143+
assert mock() == 1, "First call did not return 1."
144+
assert mock() == 2, "Second call did not return 2."
145+
assert mock() == 3, "Third call did not return 3."
146+
with upytest.raises(StopIteration):
147+
mock()
148+
149+
def test_init_mock_with_invalid_side_effect():
17150
"""
18-
A Mock object should be created with the specified attributes.
151+
If an invalid side effect is specified, a TypeError should be raised.
19152
"""
20-
pass
153+
mock = Mock(side_effect=1)
154+
with upytest.raises(TypeError):
155+
mock()
21156

22-
def test_init_mock_with_spec_and_values():
157+
def test_init_mock_with_return_value():
23158
"""
24-
A Mock object should be created with the specified attributes and values.
159+
A Mock object should be created with the specified return value that is
160+
returned each time the mock object is called.
25161
"""
26-
pass
162+
mock = Mock(return_value=42)
163+
assert mock() == 42, "Return value not as expected."

tests/test_patch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""
22
Tests the patch decorator/context manager.
3-
"""
3+
"""

umock.py

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,19 @@ class or instance) that acts as the specification for the mock
7878
if not name.startswith("_")
7979
and not callable(getattr(spec, name))
8080
]
81-
self.__class__ = spec.__class__
81+
if type(spec) is not type:
82+
# Set the mock object's class to that of the spec object.
83+
self.__class__ = type(spec)
84+
for name in self._spec:
85+
# Create a new mock object for each attribute in the spec.
86+
setattr(self, name, Mock())
8287
if return_value:
8388
self.return_value = return_value
8489
if side_effect:
85-
self.side_effect = side_effect
90+
if type(side_effect) in (str, list, tuple, set, dict):
91+
self.side_effect = iter(side_effect)
92+
else:
93+
self.side_effect = side_effect
8694
self.reset_mock()
8795
for key, value in kwargs.items():
8896
setattr(self, key, value)
@@ -198,49 +206,47 @@ def assert_never_called(self):
198206

199207
def __call__(self, *args, **kwargs):
200208
"""
201-
Record the call.
209+
Record the call and return the specified result.
210+
211+
In order of precedence, the return value is determined as follows:
212+
213+
If a side_effect is specified then that is used to determine the
214+
return value. If a return_value is specified then that is used. If
215+
neither are specified then the same Mock object is returned each time.
202216
"""
203217
self._calls.append(("__call__", args, kwargs))
204218
if hasattr(self, "side_effect"):
205-
if callable(self.side_effect):
206-
return self.side_effect(*args, **kwargs)
207-
elif isinstance(self.side_effect, Exception):
219+
if type(self.side_effect) is type and issubclass(
220+
self.side_effect, BaseException
221+
):
222+
raise self.side_effect()
223+
elif isinstance(self.side_effect, BaseException):
208224
raise self.side_effect
209-
elif hasattr(self.side_effect, "__iter__"):
210-
return self.side_effect.pop(0)
211-
elif hasattr(self, "return_value"):
212-
return self.return_value
225+
elif hasattr(self.side_effect, "__next__"):
226+
return next(self.side_effect)
227+
elif callable(self.side_effect):
228+
return self.side_effect(*args, **kwargs)
229+
raise TypeError("The mock object has an invalid side_effect.")
230+
if hasattr(self, "return_value"):
231+
print("YES")
232+
#return self.return_value
213233
else:
214234
return Mock()
215235

216236
def __getattr__(self, name):
217237
"""
218-
Return a callable that records the call.
238+
Return an attribute.
219239
"""
220240
if name.startswith("_"):
221241
return super().__getattr__(name)
222-
if hasattr(self, "return_value"):
223-
return self.return_value
242+
elif name in self.__dict__:
243+
return self.__dict__[name]
244+
elif hasattr(self, "_spec") and name not in self._spec:
245+
raise AttributeError(f"Mock object has no attribute '{name}'.")
224246
else:
225-
return Mock()
226-
227-
def __setattr__(self, name, value):
228-
"""
229-
Set an attribute on the mock object.
230-
"""
231-
if name.startswith("_"):
232-
super().__setattr__(name, value)
233-
if hasattr(self, "_spec") and name not in self._spec:
234-
raise AttributeError(f"{name} is not in the mock's spec.")
235-
super().__setattr__(name, value)
236-
237-
def __delattr__(self, name):
238-
"""
239-
Delete an attribute on the mock object.
240-
"""
241-
if hasattr(self, "_spec") and name not in self._spec:
242-
raise AttributeError(f"{name} is not in the mock's spec.")
243-
super().__delattr__(name)
247+
new_mock = Mock()
248+
setattr(self, name, new_mock)
249+
return new_mock
244250

245251

246252
class AsyncMock(Mock):

0 commit comments

Comments
 (0)