从 __getattr__ 方法中检索到的模拟函数

Mocking functions retrieved from __getattr__ method

我正在自动执行一些存储库操作,我正在使用 GitPython 完成这项工作。让我们简化一下,假设我想断言我的函数是否在存储库上调用了 pull 方法。代码如下:

from pytest_mock import MockFixture
from git import Git, Repo

repo = Repo('/Users/Jatimir/path/to/repo')

def pull() -> None:
    repo.git.pull()

但是,我注意到 Git class 有点特殊,没有实现 pull。相反,它 "delegates" 所有流量到 __getattr__,它使用另一种方法来完成这项工作。

def __getattr__(self, name):
    ...
    return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)

我的问题是如何进行测试?我正在使用 pytest with pytest-mock 提供 mocker 固定装置,这是我的尝试:

def test_pull1(mocker: MockFixture) -> None:
    pull_mock = mocker.MagicMock(name='pull')
    getattr_mock = mocker.MagicMock(name='__getattr__', return_value=pull_mock)

    mocker.patch.object(Git, '__getattr__', getattr_mock)
    pull()
    pull_mock.assert_called_once_with()


def test_pull2(mocker: MockFixture) -> None:
    pull_mock = mocker.Mock(name='pull')

    def __getattr__(self, name):
        if name == 'pull':
            return pull_mock

    mocker.patch.object(Git, '__getattr__', __getattr__)
    pull()
    pull_mock.assert_called_once_with()

它们都有效,但我觉得有更好的方法,也许我的测试方法是错误的。

感谢 jonrsharpe 指导我使用 create 参数,我设法用以下代码实现了我想要的:

def test_pull(mocker: MockFixture) -> None:
    m = mocker.patch.object(Git, 'pull', create=True)
    pull()
    m.assert_called_once_with()

摘自 documentation 解释 create=True 的作用:

By default patch() will fail to replace attributes that don’t exist. If you pass in create=True, and the attribute doesn’t exist, patch will create the attribute for you when the patched function is called, and delete it again afterwards.