Tagc Tagc - 1 year ago 91
Python Question

Mock property return value gets overridden when instantiating mock object


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

def received_response(self):
return self.__received_response

def establish_communication(self, hostname: str, port: int) -> None:

def send_request(self, request: str) -> None:


The difficulty I'm having is that when I patch
, I also try to set a
for the
attribute that will return a specific value. When I instantiate this class in my production code, however, I'm finding that calls to
are returning the default
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.

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

I expect
to be an instance of
(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
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:
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')


Answer Source

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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download