Python возвращает объект MagicMock вместо return_value
У меня есть файл python a.py
, который содержит два класса A
и B
.
class A(object):
def method_a(self):
return "Class A method a"
class B(object):
def method_b(self):
a = A()
print a.method_a()
Я хотел бы unittest method_b
в классе B
путем издевательства A
. Вот содержание файла testa.py
для этой цели:
import unittest
import mock
import a
class TestB(unittest.TestCase):
@mock.patch('a.A')
def test_method_b(self, mock_a):
mock_a.method_a.return_value = 'Mocked A'
b = a.B()
b.method_b()
if __name__ == '__main__':
unittest.main()
Я ожидаю получить Mocked A
на выходе. Но я получаю:
<MagicMock name='A().method_a()' id='4326621392'>
Где я делаю неправильно?
Ответы
Ответ 1
Когда вы @mock.patch('a.A')
, вы заменяете класс A
в тестируемом коде mock_a
.
В B.method_b
вы установите a = A()
, который теперь a = mock_a()
- i.e A
является return_value
of mock_a
. Поскольку вы не указали это значение, оно является регулярным MagicMock
; это также не настроено, поэтому вы получаете ответ по умолчанию (еще один MagicMock
) при вызове методов на нем.
Вместо этого вы хотите настроить return_value
of mock_a
на соответствующий метод, который вы можете сделать так:
mock_a().method_a.return_value = 'Mocked A'
# ^ note parentheses
или, возможно, более явно:
mock_a.return_value.method_a.return_value = 'Mocked A'
Ваш код работал бы в случае a = A
(назначая класс, а не создавая экземпляр), так как тогда a.method_a()
вызвал бы ваш макетный метод.
Ответ 2
Я предпочитаю pytest с приспособлением пересмешника. Вот тот же тест, использующий pytest и mocker:
import a
class TestB:
def test_method_b(self, mocker):
mock_A = mocker.MagicMock(name='A', spec=a.A)
mocker.patch('a.A', new=mock_A)
mock_A.return_value.method_a.return_value = 'Mocked A'
b = a.B()
b.method_b()
Вы можете найти способ, которым я написал тест, более интересным, чем сам тест - я создал библиотеку Python, чтобы помочь мне с синтаксисом.
Вот как я систематически подходил к вашей проблеме:
Мы начнем с необходимого вам теста и моей вспомогательной библиотеки:
import a
from mock_autogen.pytest_mocker import PytestMocker
class TestB:
def test_method_b(self, mocker):
# this would output the mocks we need
print(PytestMocker(a).mock_classes().prepare_asserts_calls().generate())
# your original test, without the mocks
b = a.B()
b.method_b()
Теперь тест не делает много, но вывод на печать полезен:
# mocked classes
mock_A = mocker.MagicMock(name='A', spec=a.A)
mocker.patch('a.A', new=mock_A)
mock_B = mocker.MagicMock(name='B', spec=a.B)
mocker.patch('a.B', new=mock_B)
# calls to generate_asserts, put this after the 'act'
import mock_autogen
print(mock_autogen.generator.generate_asserts(mock_A, name='mock_A'))
print(mock_autogen.generator.generate_asserts(mock_B, name='mock_B'))
Теперь я размещаю один макет для A
до вызова B()
и после секции generate_asserts
, вот так (нет необходимости в предыдущем отпечатке, поэтому я удалил его):
def test_method_b(self, mocker):
# mocked classes
mock_A = mocker.MagicMock(name='A', spec=a.A)
mocker.patch('a.A', new=mock_A)
# your original test, without the mocks
b = a.B()
b.method_b()
# calls to generate_asserts, put this after the 'act'
import mock_autogen
print(mock_autogen.generator.generate_asserts(mock_A, name='mock_A'))
После этого теста мы получили ценный вклад:
assert 1 == mock_A.call_count
mock_A.assert_called_once_with()
mock_A.return_value.method_a.assert_called_once_with()
mock_A.return_value.method_a.return_value.__str__.assert_called_once_with()
Первые две строки проверяют, что макет A
был инициализирован один раз, без параметров. Третья строка подтверждает, что был вызван method_a
, в то время как 4-я строка может быть наиболее полезной для вас и могла бы сэкономить вам много времени на самостоятельное выяснение этого:
mock_A.return_value.method_a.return_value.__str__.assert_called_once_with()
Вы видите, что возвращенное значение method_a
было применено с str
(из-за функции print
). заменить его на желаемую строку довольно просто:
mock_A.return_value.method_a.return_value = 'Mocked A'
И вот как я получил полный метод испытаний, упомянутый выше.