@Patch装饰器与pytest fixture不兼容。

70

我遇到了一些神秘的事情,当使用mock包中的patch修饰符与pytest fixture集成时。

我有两个模块:

    -----test folder
          -------func.py
          -------test_test.py

在 func.py 文件中:

    def a():
        return 1

    def b():
        return a()     

在test_test.py文件中:

    import pytest
    from func import a,b
    from mock import patch,Mock

    @pytest.fixture(scope="module")
    def brands():
        return 1


    mock_b=Mock()

    @patch('test_test.b',mock_b)
    def test_compute_scores(brands):                 
         a()

看起来 patch 装饰器与 pytest fixture 不兼容。有人对此有见解吗?谢谢。


1
我遇到了类似的问题,我同时导入了 from unittest.mock import patchimport mock ,我不得不删除 import mock 语句,然后就不再抛出 fixture 'mocked_instance' not found 错误了。 - Monsters X
1
我建议您切换接受的答案。 - gerrit
7个回答

145

当使用pytest的fixturemock.patch时,测试参数顺序至关重要。

如果将fixture参数放在模拟参数之前:

from unittest import mock

@mock.patch('my.module.my.class')
def test_my_code(my_fixture, mocked_class):

然后模拟对象将在my_fixture中,mocked_class将作为一个fixture进行搜索:

fixture 'mocked_class' not found

但是,如果您改变顺序,将装置参数放在末尾:

from unittest import mock

@mock.patch('my.module.my.class')
def test_my_code(mocked_class, my_fixture):

那么一切都会好起来的。


3
这是解决方案,不是Konrad提出的那一个。 - Ender
这应该是解决方案。 - madawa
1
在类内的测试方法中对我无效。 - Stefan
作为替代方案,您可以使用with块。这也适用于类内部。请参见下面的答案。 - Stefan

7

希望这篇对于一个老问题的回答能对某些人有所帮助。

首先,问题中没有包含错误信息,所以我们并不真正知道问题出在哪里。但我会尝试提供一些帮助。

如果你想让一个被修补过的对象来装饰一个测试用例,在使用pytest时,你可以这样做:

@mock.patch('mocked.module')
def test_me(*args):
    mocked_module = args[0]

或者对于多个补丁:

@mock.patch('mocked.module1')
@mock.patch('mocked.module')
def test_me(*args):
    mocked_module1, mocked_module2 = args

pytest需要查找测试函数/方法中要查找的fixture名称。提供*args参数能够很好地绕过查找阶段。因此,要包含带有补丁的fixture,可以这样做:

# from question
@pytest.fixture(scope="module")
def brands():
    return 1

@mock.patch('mocked.module1')
def test_me(brands, *args):
    mocked_module1 = args[0]

这对于我来说很有用,运行Python 3.6和Pytest 3.0.6时有效。

不幸的是,这个问题涉及混合使用 pytest fixtures 和 patches 在方法签名中,而不仅仅是 patches。 - zalpha314
我添加了关于固定装置的部分,这完善了对问题的回答。我会说它有点投机取巧,但它有效。感谢@zalpha314指出这一点。 - 404

6
截至Python3.3mock模块已被合并到unittest库中。此外,还有一个后移版本(适用于以前的Python版本),作为独立的mock库可用。
在同一测试套件中组合这两个库会产生上述错误:
E       fixture 'fixture_name' not found

在您的测试套件虚拟环境中,运行pip uninstall mock,并确保您没有在核心unittest库旁边使用后移植的库。在卸载后重新运行测试,如果是这种情况,您将看到ImportError
将此导入的所有实例替换为from unittest.mock import <stuff>

5

如果您有多个要应用的补丁,请注意它们注入的顺序很重要:

# from question
@pytest.fixture(scope="module")
def brands():
    return 1

# notice the order
@patch('my.module.my.class1')
@patch('my.module.my.class2')
def test_list_instance_elb_tg(mocked_class2, mocked_class1, brands):
    pass

3
这并不是直接回答你的问题,但有一个名为 pytest-mock 的插件可以让你编写如下代码:
def test_compute_scores(brands, mock):                 
     mock_b = mock.patch('test_test.b')
     a()

1

a) 对我来说,解决方案是在测试函数内部使用with块,而不是在测试函数之前使用@patch装饰器:

class TestFoo:

    def test_baa(self, my_fixture):
        with patch(
            'module.Class.function_to_patch',
            MagicMock(return_value='mocked_result')
        ) as mocked_function_to_patch:
            result= my_fixture.baa('mocked_input')
            assert result == 'mocked_result'
            mocked_function_to_patch.assert_has_calls([
                call('mocked_input')
            ])

这个解决方案可以在类中正常工作(用于组织/分组我的测试方法)。使用with语句块,您无需担心参数的顺序。我发现这比注入机制更加明确,但是如果您需要修补多个变量,则代码会变得丑陋。如果您需要修补许多依赖项,那可能意味着您要测试的函数做了太多的事情,因此您应该对其进行重构,例如提取一些功能到额外的函数中。

b) 如果您在类外部并且希望在测试方法中作为额外参数注入修补的对象... 请注意@patch不支持将模拟对象定义为装饰器的第二个参数:

@patch('path.to.foo', MagicMock(return_value='foo_value')) 
def test_baa(self, my_fixture, mocked_foo):

不起作用。

=> 确保将路径作为唯一参数传递给装饰器。然后在测试函数内定义返回值:

@patch('path.to.foo') 
def test_baa(self, my_fixture, mocked_foo):
    mocked_foo.return_value = 'foo_value'

不幸的是,在类内部似乎无法使用这个方法。

首先注入fixture(s)然后注入@patch装饰器的变量(例如“mocked_foo”)。

注入的fixture名称“my_fixture”需要正确。它必须与修饰fixture函数的名称(或fixture装饰中使用的显式名称)匹配。

注入的patch变量名称“mocked_foo”没有遵循明确的命名规则。您可以根据需要选择名称,独立于相应的@patch装饰器路径。

如果您注入几个打补丁的变量,请注意顺序相反:属于最后一个@patch装饰器的模拟实例首先被注入:

@patch('path.to.foo') 
@patch('path.to.qux') 
def test_baa(self, my_fixture, mocked_qux, mocked_foo):
    mocked_foo.return_value = 'foo_value'

-7

我曾经遇到同样的问题,对我来说解决办法是使用1.0.1版本的模拟库(之前我使用的是2.6.0版本的unittest.mock)。现在它已经完美运行了 :)


谢谢您的回答。我在使用Mock库1.0.1时遇到了这个问题。 - Hello lad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接