Using Python mock to spy on calls to an existing object
I'm using the Python mock module for tests. I would like to replace an active object with a mock, and automatically have all calls made to the mock object forwarded to the original object. I think this is called a "Spy" in standard testing terminology. At the moment I'm doing inside a test:
# Insert a mock replacement
orig_active_attr = server.active_attr
server.active_attr = mock.Mock()
# Set up side effects to 'proxy' to the original object
server.active_attr.meth1.side_effect = orig_active_attr.meth1
server.active_attr.meth2.side_effect = orig_active_attr.meth2
# Call the method being tested
server.method_being_tested()
# Assert stuff on the mock.
server.active_attr.meth2.assert_called_once()
It would be nice if all method calls on the mock could be forwarded to the live object automatically without the boilerplate.
I seem to have stumbled across the solution:
import mock
class A(object):
def meth(self, a):
return a
a = A()
ma = mock.Mock(wraps=a)
Seems to work okay for functions, methods and properties, but not for class or instance attributes.
See the documentation.
You can use patch.object(wraps=obj_instance)
as suggested in Spying on instance methods with Python's mock module.
For example:
from mock import patch
class Foo(object):
def bar(self, x, y):
return x + y + 1
def test_bar():
foo = Foo()
with patch.object(foo, 'bar', wraps=foo.bar) as wrapped_foo:
foo.bar(1, 2)
wrapped_foo.assert_called_with(1, 2)
Here's how to mock only datetime.date.today()
, forwarding the rest of datetime
calls to the datetime
module:
from unittest import mock, TestCase
import foo_module
class FooTest(TestCase):
@mock.patch(f'{foo_module.__name__}.datetime', wraps=foo_module.datetime)
def test_something(self, mock_datetime):
# mock only datetime.date.today()
mock_datetime.date.today.return_value = datetime.date(2019, 3, 15)
# other calls to datetime functions will be forwarded to original datetime
foo_module
imports datetime
and uses many other datetime
functions besides date.today
.
You can use a simple function to iterate through all the method and configure your mock
def spy_mock(instance):
members = inspect.getmembers(instance, inspect.ismethod)
attrs = {'%s.side_effect' % k:v for k,v in members}
return mock.Mock(**attrs)
Usage would be
import inspect
from unittest import mock
class ActiveAttr:
def meth2(self):
print("Meth2 called")
class Server:
def __init__(self):
self.active_attr = ActiveAttr()
def method_being_tested(self):
self.active_attr.meth2()
def spy_mock(instance):
members = inspect.getmembers(instance, inspect.ismethod)
attrs = {'%s.side_effect' % k:v for k,v in members}
return mock.Mock(**attrs)
server = Server()
server.active_attr = spy_mock(server.active_attr)
server.method_being_tested()
server.active_attr.meth2.assert_called_once()
Extending upon the pattern from Wes McKinney (via Wilfred Hughes Answer), here is how to spy on a method/member of an object, where object is imported into module under test.
Note this solution is Python 2.x compliant!
module under test:
import object
def function_in_module_under_test():
object.method_from_imported_object()
testing spied assertion on method_from_imported_object:
from unittest import mock
import module_under_test
def test_method(self):
with mock.patch.object(
module_under_test.object,
"method_from_imported_object",
module_under_test.object.method_from_imported_object,
) as spy_method_from_imported_object:
# Demonstrate that subsequent spy asserts here, can be trusted
spy_method_from_imported_object.assert_not_called()
# now Test
module_under_test.function_in_module_under_test()
spy_method_from_imported_object.assert_called_once()