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.
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
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)