简体   繁体   English

Python 返回 MagicMock 对象而不是 return_value

[英]Python returns MagicMock object instead of return_value

I have a python file a.py which contains two classes A and 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_b in class B by mocking A .我想通过模拟AB类中对method_b进行单元测试。 Here is the content of the file testa.py for this purpose:这是用于此目的的文件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 A in the output.我希望在输出中得到Mocked A But what I get is:但我得到的是:

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

Where am I doing wrong?我在哪里做错了?

When you @mock.patch('a.A') , you are replacing the class A in the code under test with mock_a .当您@mock.patch('a.A')时,您正在用mock_a替换被测代码中的类A

In B.method_b you then set a = A() , which is now a = mock_a() - ie a is the return_value of mock_a .然后在B.method_b设置a = A() ,现在a = mock_a() - 即amock_areturn_value As you haven't specified this value, it's a regular MagicMock ;由于您尚未指定此值,因此它是常规MagicMock this isn't configured either, so you get the default response (yet another MagicMock ) when calling methods on it.这也没有配置,所以在调用它的方法时你会得到默认响应(又一个MagicMock )。

Instead, you want to configure the return_value of mock_a to have the appropriate method, which you can do as either:相反,您希望将mock_areturn_value配置为具有适当的方法,您可以这样做:

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()会触发您的模拟方法。

I prefer pytest with mocker fixture . 我更喜欢用测试器夹具进行 pytest Here is the same test, using pytest and mocker: 这是使用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 library to 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 A before the call to B() and the generate_asserts section after, like so (no need for the previous print, so I removed it): 现在,我在调用B()和之后的generate_asserts部分之前为A放置了一个模拟器,就像这样(不需要先前的打印,所以我将其删除):

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 A mock was initialized once, without parameters. 前两行验证A mock是否已初始化一次,没有参数。 The third line verifies method_a was called, while the 4th line may be the most helpful for you and could have saved you much time figuring this on your own: 第三行验证了method_a被调用,而第四行可能对你最有帮助,并且可以节省你很多时间自己解决这个问题:

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

You see that the returned value of method_a has been applied with str (due to the print function). 您会看到method_a的返回值已应用于str (由于print功能)。 replacing that with your desired string is pretty easy: 用你想要的字符串替换它很容易:

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

And that's how I got to the full test method mentioned above. 这就是我如何进入上面提到的完整测试方法。

In case of mocking an object I use this syntax:在模拟对象的情况下,我使用以下语法:

@mock.patch.object(
    a.A,
    'method_a',
    lambda a: "Mocked A")
def test_method_b(self):
    b = a.B()
    b.method_b()

In this case the method_a is mocked by the lambda function.在这种情况下,method_a 被 lambda 函数模拟。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM