当我无权访问实例本身时,如何设置模拟 class 实例的 return_value?

how do I set return_value of a mocked class instance when I don't have access to the instance itself?

假设我有一些函数 A.foo() 实例化并使用 B 的实例,调用它的成员函数 bar

当我测试我的 A class 时,如何在 B 的模拟实例上设置 return_value,因为我无权访问B 的实例?也许一些代码可以更好地说明这一点:

import unittest
import unittest.mock
import pandas

class A:
    def foo(self):
        b = B()
        return b.bar()

class B:
    def bar():
        return 1

@unittest.mock.patch("__main__.B")
class MyTestCase(unittest.TestCase):
    def test_case_1(self, MockB):
        
        MockB.bar.return_value = 2
        
        a = A()
        
        self.assertEqual(a.foo(), 2)

test_case = MyTestCase()
test_case.test_case_1()

这失败了;

AssertionError: <MagicMock name='B().bar()' id='140542513129176'> != 2

显然 MockB.bar.return_value = 2 行没有修改方法的 return 值。

不用mock.patch也能解决这个问题。更改 foo 方法以接受它应该构建的依赖项的工厂 (DI)。

class A:
    def foo(self, b_factory: 'Callable[[], B]' = B):
        b = b_factory()
        return b.bar()

def normal_code():
    a = A()
    assert a.foo() == ...

def test():
    dummy_b = ...  # build a dummy object here however you like
    
    a = A()
    assert a.foo(b_factory=lambda: dummy_b) == 2

我认为你没有启动 MockB。可以直接mock "main.B.bar":

@unittest.mock.patch("__main__.B.bar")
class MyTestCase(unittest.TestCase):
    def test_case_1(self, MockB):
        
        MockB.return_value = 2
        
        a = A()
        
        self.assertEqual(a.foo(), 2)

您的代码中只有 1 个错误。替换此行:

MockB.bar.return_value = 2

收件人:

MockB.return_value.bar.return_value = 2

它会起作用。


我假设您粘贴的这段代码只是一个玩具示例。如果 class AB 位于另一个文件中,例如src/somedir/somefile.py,不要忘记修补完整路径。

@unittest.mock.patch("src.somedir.somefile.B")
class MyTestCase(unittest.TestCase):
    ...

更新

为了进一步展开,您可以在 docs:

中看到一些用法
>>> class Class:
...     def method(self):
...         pass
...
>>> with patch('__main__.Class') as MockClass:
...     instance = MockClass.return_value
...     instance.method.return_value = 'foo'
...     assert Class() is instance
...     assert Class().method() == 'foo'
...

所以在你的情况下:

  • MockB.bar.return_value 就像调用静态方法,例如print(MockB.bar())
  • MockB.return_value.bar.return_value 就像调用 class/instance 方法,例如print(MockB().bar())

形象化:

import unittest.mock


class SomeClass:
    def method(self):
        return 1


@unittest.mock.patch("__main__.SomeClass")
def test_mock(mock_class):
    print(mock_class)
    print(mock_class.return_value)

    mock_class.method.return_value = -10
    mock_class.return_value.method.return_value = -20

    print(SomeClass.method())
    print(SomeClass().method())


test_mock()
$ python3 test_src.py 
<MagicMock name='SomeClass' id='140568144584128'>
<MagicMock name='SomeClass()' id='140568144785952'>
-10
-20
  • 如您所见,mock_class.return_value 是用于 SomeClass().method().
  • 等实例操作的那个