Python 返回 MagicMock 对象而不是 return_value

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/38199008/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 20:30:23  来源:igfitidea点击:

Python returns MagicMock object instead of return_value

pythonpython-unittestmagicmock

提问by Mehdi Jafarnia Jahromi

I have a python file a.pywhich contains two classes Aand B.

我有一个 python 文件a.py,其中包含两个类AB.

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

I would like to unittest method_bin class Bby mocking A. Here is the content of the file testa.pyfor this purpose:

我想通过模拟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()

I expect to get Mocked Ain the output. But what I get is:

我希望得到Mocked A输出。但我得到的是:

<MagicMock name='A().method_a()' id='4326621392'>

Where am I doing wrong?

我哪里做错了?

回答by jonrsharpe

When you @mock.patch('a.A'), you are replacing the class Ain the code under test with mock_a.

当您@mock.patch('a.A'),您要更换类A下与测试代码mock_a

In B.method_byou then set a = A(), which is now a = mock_a()- i.e. ais the return_valueof mock_a. As you haven't specified this value, it's a regular MagicMock; this isn't configured either, so you get the default response (yet another MagicMock) when calling methods on it.

B.method_b你接盘a = A(),这是现在a = mock_a()-即areturn_valuemock_a。由于您尚未指定此值,因此它是一个常规MagicMock; 这也未配置,因此MagicMock在调用其上的方法时会获得默认响应(还有另一个)。

Instead, you want to configure the return_valueof mock_ato have the appropriate method, which you can do as either:

相反,您希望return_valueofmock_a配置为具有适当的方法,您可以执行以下任一操作:

mock_a().method_a.return_value = 'Mocked A' 
    # ^ note parentheses

or, perhaps more explicitly:

或者,也许更明确:

mock_a.return_value.method_a.return_value = 'Mocked A'

Your code would have worked in the case a = A(assigning the class, not creating an instance), as then a.method_a()would have triggered your mock method.

您的代码在这种情况下会起作用a = A(分配类,而不是创建实例),因为那样a.method_a()会触发您的模拟方法。

回答by Peter K

I prefer pytestwith mocker fixture. Here is the same test, using pytest and mocker:

我更喜欢带有mocker fixture 的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()

You may find the way I wrote the test more interesting than the test itself - I have created a python libraryto help me with the syntax.

您可能会发现我编写测试的方式比测试本身更有趣——我创建了一个python 库来帮助我处理语法。

Here is how I approached your problem in a systematic way:

以下是我系统地解决您的问题的方法:

We start with the test you want and my helper library:

我们从你想要的测试和我的帮助库开始:

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

Now the test doesn't do much, but the print output is useful:

现在测试没有做太多,但打印输出很有用:

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

Now, I'm placing a single mock for Abefore the call to B()and the generate_assertssection after, like so (no need for the previous print, so I removed it):

现在,我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'))

After this test execution we have gained some valuable input:

执行此测试后,我们获得了一些有价值的输入:

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

The first two lines verifies the Amock was initialized once, without parameters. The third line verifies method_awas called, while the 4th line may be the most helpful for you and could have saved you much time figuring this on your own:

前两行验证A模拟已初始化一次,没有参数。method_a调用了第三行验证,而第 4 行可能对您最有帮助,并且可以为您节省很多时间来自己解决这个问题:

mock_A.return_value.method_a.return_value.__str__.assert_called_once_with()

You see that the returned value of method_ahas been applied with str(due to the printfunction). replacing that with your desired string is pretty easy:

您会看到 的返回值method_a已被应用str(由于print函数)。用您想要的字符串替换它非常简单:

mock_A.return_value.method_a.return_value = 'Mocked A'

And that's how I got to the full test method mentioned above.

这就是我如何获得上面提到的完整测试方法。