Возвращаемое значение фиктивного свойства переопределяется при создании фиктивного объекта

Фон

Я пытаюсь настроить тестовое приспособление для приложения, которое я пишу, в котором один из моих классов заменен макетом. Я рад оставить большинство атрибутов фиктивного класса в качестве экземпляров 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, я также пытаюсь установить PropertyMock для атрибута received_response, который будет возвращать определенное значение. Однако, когда я создаю экземпляр этого класса в своем производственном коде, я обнаруживаю, что вызовы 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_response будет экземпляром Subject (возвращаемое значение макета свойства). Однако вместо этого я получаю следующее:

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

Похоже, проблема заключается в том, что фиктивное свойство экземпляра, возвращенного методом patch, работает нормально, но фиктивные свойства искажаются при создании нового экземпляра исправленного класса.

SSCCE

Я считаю, что приведенный ниже фрагмент отражает суть этой проблемы. Если есть способ изменить приведенный ниже сценарий, чтобы 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()

person Tagc    schedule 04.09.2016    source источник


Ответы (2)


Следующая строка:

type(mock_foo).bar = mock_bar

издевается над mock_foo, которое в этот момент является возвращаемым значением enter_context. Если я правильно понимаю документацию, это означает, что вы теперь фактически обрабатывается результат __enter__ возвращаемого значения patch('__main__.Foo', spec=True).

Если вы измените эту строку на:

type(Foo.return_value).bar = mock_bar

тогда вы будете издеваться над свойством bar экземпляров Foo (поскольку возвращаемое значение вызова класса является экземпляром). Затем второй оператор печати напечатает mock value, как и ожидалось.

person Simeon Visser    schedule 04.09.2016
comment
Здоровья, приятель. :) - person Tagc; 04.09.2016

Это не ответ на вопрос, а мое решение более простой проблемы, полученное из ответа Симеона.

В моем случае я хотел издеваться над 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
person Chris    schedule 15.03.2021