Pytest - 在 mock 的嵌套属性函数/方法上模拟 side_effect

Pytest - mocking a side_effect on mock's nested attribute function / method

我有一个像这样模拟外部库的夹具,使用 pytest-mock,它是 unittest.mock 的包装器。

# client.py

import Test as TestLibrary

class LibraryName():
   def get_client():
      return TestLibrary.Library()

# library_service.py

def using_library():
   '''
   Edited note: Library().attribute behind the scenes is set to
   self.attribute = Attribute() 
   so this may be affecting the mocking
   '''
   client = LibraryName.get_client()
   return client.attribute.method()

# conftest.py

@pytest.fixture
def library_client_mock(mocker):
    import Test as TestLibrary

    return mocker.patch.object(TestLibrary, 'Library')

# test_library_service.py

def test_library_method(library_client_mock):
   result = using_library()

我可以像这样模拟一个 return 值:

def test_library_method(library_client_mock):
   library_client_mock.return_value.attribute.return_value.method.return_value = "test"
   result = using_library()
   assert result == "test"

但我不能用 side_effect

模拟抛出异常
def test_library_method(library_client_mock):
    library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError # doesn't work
    library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError() # doesn't work
    attrs = { 'attribute.method.side_effect': TypeError }
    library_client_mock.configure_mock(**attrs) # doesn't work

    with pytest.raises(TypeError):   
        using_library() # fails assertion

我在这里遗漏了什么?

这些是您的代码中的错误:

  1. 变化:

    library_client_mock.return_value.attribute.return_value.method.return_value = "test"
    

    收件人:

    library_client_mock.return_value.attribute.method.return_value = "test"
    
  2. 变化:

    library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError
    

    收件人:

    library_client_mock.return_value.attribute.method.side_effect = TypeError
    

说明

.return_value 只能用于可调用对象,例如函数 documented:

return_value

Set this to configure the value returned by calling the mock:

>>> mock = Mock()
>>> mock.return_value = 'fish'
>>> mock()
'fish'

因此,您只能将 .return_value 用于以下情况:

  • TestLibrary.Library()
  • TestLibrary.Library().attribute.method()

但不适用于:

  • TestLibrary.Library().attribute

因为 .attribute 不是可调用的,例如TestLibrary.Library().attribute().

警告

您修补 Library 的方式是通过其位于 Test.Library 的源位置(或别名为 TestLibrary.Library)。具体通过:

import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')

它目前有效,因为您导入和使用它的方式是通过根路径。

# client.py
import Test as TestLibrary
...
    return TestLibrary.Library()
...

但是如果我们改变我们导入那个库的方式并导入一个本地版本到client.py:

# client.py
from Test import Library  # Instead of <import Test as TestLibrary>
...
    return Library()  # Instead of <TestLibrary.Library()>
...

它现在会失败。理想情况下,您应该 patch the specific name that is used by the system under test,这里是 client.Library.

import client
return mocker.patch.object(client, 'Library')

除非您确定将使用该库的所有文件都将只导入根目录而不是本地版本。

@Niel Godfrey Ponciano 使用 side_effect

的语法让我走上正确的道路
library_client_mock.return_value.attribute.method.side_effect = TypeError

但这还不够。

# conftest.py

@pytest.fixture
def library_client_mock(mocker):
    import Test as TestLibrary

    return mocker.patch.object(TestLibrary, 'Library')

我不得不添加一个额外的模拟:

# conftest.py

@pytest.fixture
def library_client_mock(mocker):
    import Test as TestLibrary

    mock_library_client = mocker.patch.object(TestLibrary, 'Library')
    # option 1
    mock_attribute = Mock()
    # option 2, path to Library.attribute = Attribute()
    mock_attribute = mocker.patch.object(TestLibrary.services, 'Attribute', autospec=True)
    mock_library_client.attach_mock(mock_attribute, "attribute")
    return mock_library_client

然后以下两个语句按预期工作。虽然我不确定为什么 return_value 在没有附加模拟的情况下开箱即用,但 side_effect 却没有。

# return_value set correctly
# NOTE return_value needed after each
library_client_mock.return_value.attribute.return_value.method.return_value = "test"

# side_effect set correctly
# NOTE return_value not needed after "attribute"
library_client_mock.return_value.attribute.method.side_effect = TypeError