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

Pending changes exported from your codespace #1

Merged
merged 1 commit into from
Jul 18, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/test_asyncmock.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""
Ensure AsyncMock works as expected.
"""
"""
153 changes: 145 additions & 8 deletions tests/test_mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,161 @@
exacty the same as unittest.mock.Mock).
"""

import upytest
from umock import Mock


def test_init_mock():
"""
A Mock object should be created with no attributes.
A plain Mock object can be created with no arguments. Accessing arbitrary
attributes on such an object (without a spec) should return another Mock
object.
"""
mock = Mock()
assert mock.__dict__ == {}, "Not an empty dict."
assert mock.call_count == 0, "Non zero call count with new Mock object."
assert isinstance(
mock.foo, Mock
), "Attribute access did not return a Mock."

def test_init_mock_with_spec():

def test_init_mock_with_spec_from_list():
"""
A Mock object should be created with the specified list of attributes.
Accessing arbitrary attributes not in the list should raise an
AttributeError.

If an arbitrary attribute is subqeuently added to the mock object, it
should be accessible as per normal Python behaviour.
"""
mock = Mock(spec=["foo", "bar"])
assert hasattr(mock, "foo"), "Mock object missing 'foo' attribute."
assert hasattr(mock, "bar"), "Mock object missing 'bar' attribute."
assert not hasattr(
mock, "baz"
), "Mock object has unexpected 'baz' attribute."
mock.baz = "test"
assert mock.baz == "test", "Mock object attribute 'baz' not set correctly."


def test_init_mock_with_spec_from_object():
"""
A Mock object should be created with the specified attributes derived from
the referenced instance. The Mock's __class__ should be set to that of the
spec object's. Accessing arbitrary attributes not on the class should raise
an AttributeError.

If an arbitrary attribute is subqeuently added to the mock object, it
should be accessible as per normal Python behaviour.
"""

class TestClass:
x = 1
y = 2

obj = TestClass()
mock = Mock(spec=obj)
assert hasattr(mock, "x"), "Mock object missing 'x' attribute."
assert hasattr(mock, "y"), "Mock object missing 'y' attribute."
assert not hasattr(mock, "z"), "Mock object has unexpected 'z' attribute."
assert mock.__class__ == TestClass, "Mock object has unexpected class."
mock.z = "test"
assert mock.z == "test", "Mock object attribute 'z' not set correctly."


def test_init_mock_with_spec_from_class():
"""
A Mock object should be created with the specified attributes derived from
the referenced class. Since this is a class spec, the Mock's __class__
remains as Mock. Accessing arbitrary attributes not on the class should
raise an AttributeError.

If an arbitrary attribute is subqeuently added to the mock object, it
should be accessible as per normal Python behaviour.
"""

class TestClass:
x = 1
y = 2

mock = Mock(spec=TestClass)
assert hasattr(mock, "x"), "Mock object missing 'x' attribute."
assert hasattr(mock, "y"), "Mock object missing 'y' attribute."
assert not hasattr(mock, "z"), "Mock object has unexpected 'z' attribute."
assert mock.__class__ == Mock, "Mock object has unexpected class."
mock.z = "test"
assert mock.z == "test", "Mock object attribute 'z' not set correctly."


def test_init_mock_with_callable_side_effect():
"""
A Mock object should be created with the specified callable side effect
that computes the result of a call on the mock object.
"""

def side_effect(a, b):
return a + b

mock = Mock(side_effect=side_effect)
assert (
mock(1, 2) == 3
), "Mock object side effect did not compute correctly."


def test_init_mock_with_exception_class_side_effect():
"""
A Mock object should be created with the specified exception class side
effect that raises the exception when the mock object is called.
"""

class TestException(Exception):
pass

mock = Mock(side_effect=TestException)
with upytest.raises(TestException):
mock()


def test_init_mock_with_exception_instance_side_effect():
"""
A Mock object should be created with the specified exception instance side
effect that is raised when the mock object is called.
"""

ex = ValueError("test")
mock = Mock(side_effect=ex)
with upytest.raises(ValueError) as expected:
mock()
assert (
str(expected.exception.value) == "test"
), "Exception message not as expected."


def test_init_mock_with_iterable_side_effect():
"""
A Mock object should be created with the specified iterable side effect
that returns the next item in the iterable each time the mock object is
called.
"""

mock = Mock(side_effect=[1, 2, 3])
assert mock() == 1, "First call did not return 1."
assert mock() == 2, "Second call did not return 2."
assert mock() == 3, "Third call did not return 3."
with upytest.raises(StopIteration):
mock()

def test_init_mock_with_invalid_side_effect():
"""
A Mock object should be created with the specified attributes.
If an invalid side effect is specified, a TypeError should be raised.
"""
pass
mock = Mock(side_effect=1)
with upytest.raises(TypeError):
mock()

def test_init_mock_with_spec_and_values():
def test_init_mock_with_return_value():
"""
A Mock object should be created with the specified attributes and values.
A Mock object should be created with the specified return value that is
returned each time the mock object is called.
"""
pass
mock = Mock(return_value=42)
assert mock() == 42, "Return value not as expected."
2 changes: 1 addition & 1 deletion tests/test_patch.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""
Tests the patch decorator/context manager.
"""
"""
70 changes: 38 additions & 32 deletions umock.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,19 @@ class or instance) that acts as the specification for the mock
if not name.startswith("_")
and not callable(getattr(spec, name))
]
self.__class__ = spec.__class__
if type(spec) is not type:
# Set the mock object's class to that of the spec object.
self.__class__ = type(spec)
for name in self._spec:
# Create a new mock object for each attribute in the spec.
setattr(self, name, Mock())
if return_value:
self.return_value = return_value
if side_effect:
self.side_effect = side_effect
if type(side_effect) in (str, list, tuple, set, dict):
self.side_effect = iter(side_effect)
else:
self.side_effect = side_effect
self.reset_mock()
for key, value in kwargs.items():
setattr(self, key, value)
Expand Down Expand Up @@ -198,49 +206,47 @@ def assert_never_called(self):

def __call__(self, *args, **kwargs):
"""
Record the call.
Record the call and return the specified result.

In order of precedence, the return value is determined as follows:

If a side_effect is specified then that is used to determine the
return value. If a return_value is specified then that is used. If
neither are specified then the same Mock object is returned each time.
"""
self._calls.append(("__call__", args, kwargs))
if hasattr(self, "side_effect"):
if callable(self.side_effect):
return self.side_effect(*args, **kwargs)
elif isinstance(self.side_effect, Exception):
if type(self.side_effect) is type and issubclass(
self.side_effect, BaseException
):
raise self.side_effect()
elif isinstance(self.side_effect, BaseException):
raise self.side_effect
elif hasattr(self.side_effect, "__iter__"):
return self.side_effect.pop(0)
elif hasattr(self, "return_value"):
return self.return_value
elif hasattr(self.side_effect, "__next__"):
return next(self.side_effect)
elif callable(self.side_effect):
return self.side_effect(*args, **kwargs)
raise TypeError("The mock object has an invalid side_effect.")
if hasattr(self, "return_value"):
print("YES")
#return self.return_value
else:
return Mock()

def __getattr__(self, name):
"""
Return a callable that records the call.
Return an attribute.
"""
if name.startswith("_"):
return super().__getattr__(name)
if hasattr(self, "return_value"):
return self.return_value
elif name in self.__dict__:
return self.__dict__[name]
elif hasattr(self, "_spec") and name not in self._spec:
raise AttributeError(f"Mock object has no attribute '{name}'.")
else:
return Mock()

def __setattr__(self, name, value):
"""
Set an attribute on the mock object.
"""
if name.startswith("_"):
super().__setattr__(name, value)
if hasattr(self, "_spec") and name not in self._spec:
raise AttributeError(f"{name} is not in the mock's spec.")
super().__setattr__(name, value)

def __delattr__(self, name):
"""
Delete an attribute on the mock object.
"""
if hasattr(self, "_spec") and name not in self._spec:
raise AttributeError(f"{name} is not in the mock's spec.")
super().__delattr__(name)
new_mock = Mock()
setattr(self, name, new_mock)
return new_mock


class AsyncMock(Mock):
Expand Down
Loading