【问题标题】:Mock property return value gets overridden when instantiating mock object实例化模拟对象时,模拟属性返回值被覆盖
【发布时间】:2016-09-04 00:31:31
【问题描述】:

背景

我正在尝试为我正在编写的应用程序设置一个测试夹具,其中我的一个类被替换为模拟。我很高兴将模拟类的大部分属性保留为默认的 MagicMock 实例(我只对它们的使用做出断言感兴趣),但该类还有一个我想提供的属性返回值。

作为参考,这是我要修补的类的大纲:

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

问题

我遇到的困难是,当我修补CommunicationService 时,我还尝试为received_response 属性设置一个PropertyMock,它将返回一个特定的值。但是,当我在生产代码中实例化此类时,我发现对 CommunicationService.received_response 的调用返回的是默认的 MagicMock 实例,而不是我希望它们返回的特定值。

在测试设置期间,我执行以下操作:

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)

在我的生产代码中(执行此设置后调用):

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

我希望comms.received_responseSubject 的一个实例(模拟属性的返回值)。但是,我得到以下信息:

<MagicMock name='CommunicationService().received_response' id='4580209944'>

问题似乎是从补丁方法返回的实例上的模拟属性工作正常,但是在创建修补类的新实例时,模拟属性会变得混乱

SSCCE

我相信下面的 sn-p 抓住了这个问题的本质。如果有办法修改下面的脚本以使print(foo.bar) 返回mock value,那么希望它会显示我如何在我的实际代码中解决问题。

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

【问题讨论】:

    标签: python python-3.x mocking python-mock


    【解决方案1】:

    下面一行:

    type(mock_foo).bar = mock_bar
    

    模拟mock_foo,此时它是enter_context 的返回值。如果我对the documentation 的理解正确,这意味着您现在实际上正在处理__enter__ 的返回值patch('__main__.Foo', spec=True) 的结果。

    如果您将该行更改为:

    type(Foo.return_value).bar = mock_bar
    

    然后您将模拟Foo 实例的属性bar(因为调用类的返回值是一个实例)。然后第二个打印语句将按预期打印mock value

    【讨论】:

    • 干杯伙伴。 :)
    【解决方案2】:

    这不是问题的答案,而是我从 Simeon 的回答中学到的更简单问题的解决方案。

    在我的情况下,我想模拟 obj.my_method().my_property 并得到 PropertyMock 作为返回值,因为我将返回值的属性直接设置为我的 PropertyMock 实例,而不是由返回的模拟的 type方法。这是固定的代码:

    with patch.object(MyObj, "my_method") as method_mock:
        property_mock = PropertyMock(return_value='foo')
        type(method_mock.return_value).my_property = property_mock
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-16
      相关资源
      最近更新 更多