I'm trying to set up a test fixture for an application I'm writing in which one of my classes is replaced with a mock. I'm happy to leave most of the attributes of the mock class as the default MagicMock
instances (where I'm only interested in making assertions about their usage), but the class also has a property that I want to provide a specific return value for.
For reference, this is the outline of the class I'm trying to patch:
class CommunicationService(object):
def __init__(self):
self.__received_response = Subject()
@property
def received_response(self):
return self.__received_response
def establish_communication(self, hostname: str, port: int) -> None:
pass
def send_request(self, request: str) -> None:
pass
The difficulty I'm having is that when I patch CommunicationService
, I also try to set a PropertyMock
for the received_response
attribute that will return a specific value. When I instantiate this class in my production code, however, I'm finding that calls to CommunicationService.received_response
are returning the default MagicMock
instances instead of the specific value I want them to return.
During test setup, I do the following:
context.mock_comms_exit_stack = ExitStack()
context.mock_comms = context.mock_comms_exit_stack.enter_context(
patch('testcube.comms.CommunicationService', spec=True))
# Make 'received_response' observers subscribe to a mock subject.
context.mock_received_response_subject = Subject()
type(context.mock_comms).received_response = PropertyMock(return_value=context.mock_received_response_subject)
# Reload TestCube module to make it import the mock communications class.
reload_testcube_module(context)
In my production code (invoked after performing this setup):
# Establish communication with TestCube Web Service.
comms = CommunicationService()
comms.establish_communication(hostname, port)
# Wire plugins with communications service.
for plugin in context.command.plugins:
plugin.on_response = comms.received_response
plugin.request_generated.subscribe(comms.send_request)
I expect comms.received_response
to be an instance of Subject
(the return value of the property mock). However, instead I get the following:
<MagicMock name='CommunicationService().received_response' id='4580209944'>
The problem seems to be that the mock property on the instance returned from the patch method works fine, but mock properties get messed up when creating a new instance of the patched class.
I believe that the snippet below captures the essence of this problem. If there's a way to modify the script below to make it so that print(foo.bar)
returns mock value
, then hopefully it'll show how I can resolve the problem in my actual code.
from contextlib import ExitStack
from unittest.mock import patch, PropertyMock
class Foo:
@property
def bar(self):
return 'real value'
exit_stack = ExitStack()
mock_foo = exit_stack.enter_context(patch('__main__.Foo', spec=True))
mock_bar = PropertyMock(return_value='mock value')
type(mock_foo).bar = mock_bar
print(mock_foo.bar) # 'mock value' (expected)
foo = Foo()
print(foo.bar) # <MagicMock name='Foo().bar' id='4372262080'> (unexpected - should be 'mock value')
exit_stack.close()
The following line:
type(mock_foo).bar = mock_bar
mocks mock_foo
which, at that point, is the return value of enter_context
. If I understand the documentation correctly it means you're now actually handling the result of __enter__
of the return value of patch('__main__.Foo', spec=True)
.
If you change that line to:
type(Foo.return_value).bar = mock_bar
then you'll mock the property bar
of instances of Foo
(as the return value of calling a class is an instance). The second print statement will then print mock value
as expected.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With