You have a class whose API you want to verify:
class Class_A(object): def method_X(self): self.method_Y() def method_Y(self): pass
You write the following test fully expecting it to pass. Alas, the method_Y
assertion fails.
def test_class_a(self): obj = Class_A() mock = MagicMock(spec_set=obj, wraps=obj) mock.method_X() mock.method_Y.assert_called_once_with()
This is because the Python Unittest Mock, while wrapping mock.method_X()
, Mock uses obj
in the __self__
of the method_Y
binding. As a result the self
value passed to method_X
is that of the obj
and not mock
and the call to method_Y
is therefore invisible to the Mock.
Enter Spy
available from Karellen Testing Library (PyPI), which solves the above problem by substituting wrapped methods in the Mock
by rebinding them through Spy
object, passing Spy
object as self
to all method calls. Since the mock
wraps the Class_A
object and Spy
wraps the mock
, Spy ensures that all internal object calls go back through the mock, allowing for call accounting and argument capture. All getattr
and setattr
calls end up going through the Spy
as well, allowing it to either retrieve the attribute of the wrapped object or delegate to the mock instead.
The magic of solution is in this line rebinding the wrapped method to use the Spy for __self__
with the rest performing field housekeeping and mock delegation as appropriate:
attr._mock_wraps = types.MethodType(mock_wraps.__func__, self)
This spying method should work for most common objects. The properly working test code will now look like and will pass.
from karellen.testing.mock import MagicSpy [...] def test_class_a_spy(self): mock = MagicSpy(Class_A()) mock.method_X() mock.method_Y.assert_called_once_with()
MagicSpy(obj)
is equivalent to:
mock = MagicMock(spec_set=obj, wraps=obj) return Spy(mock)