如何模拟未按名称调用的函数?

Posted

技术标签:

【中文标题】如何模拟未按名称调用的函数?【英文标题】:How can I mock a function that isn't called by name? 【发布时间】:2020-08-04 11:34:30 【问题描述】:

假设我有一个装饰器,它收集它所装饰的所有函数,以便在将来的某个时候调用。

mydecorator.py

class CallLater(object):
    funcs = []

    def __init__(self, func):
        self.funcs.append(func)

    @classmethod
    def call_now(cls, *args, **kwargs):
        for func in cls.funcs:
            func(*args, **kwargs)

然后,我在一个模块中有一个函数,其中一个将由我的装饰器保存。

mymodule.py

import logging

from mydecorator import CallLater

logging.basicConfig(level=logging.INFO) 

@CallLater
def log_a():
    logging.info("A")

@CallLater
def log_b():
    logging.info("B")

def log_c():
    logging.info("C")

现在,如果我导入 mymodule 并调用 CallLater.call_now(),将调用 log_alog_b。但是假设在测试期间,我希望将log_b 替换为log_c。我会尝试用模拟来替换。

mock_test.py

import logging
import pytest

from mymodule import log_a, log_c
from mydecorator import CallLater

logging.basicConfig(level=logging.INFO) 
pytest_plugins = ('pytest_mock',)

def test_mocking(mocker, caplog):
    mocker.patch('mymodule.log_b', log_c)

    CallLater.call_now()

    logs = [rec.message for rec in caplog.records]
    assert logs == ["A", "C"]

但是当我运行pytest 时,我发现我的模拟不起作用。

FAILED mock_test.py::test_mocking - AssertionError: assert ['A', 'B'] == ['A', 'C']

我认为'mymodule.log_b' 是错误的模拟目标,因为它没有被调用为mymodule.log_b(),但我不确定在这种情况下使用什么来代替。任何建议表示赞赏!

【问题讨论】:

作为参考,这个问题的灵感来自想要禁用SQLAlchemy event listeners,这似乎可以通过调用sqlalchemy.event.remove 更好地处理,如this answer 【参考方案1】:

这是不可能的,问题在于函数在加载时已经分配给列表。我能看到修补此问题的唯一方法是直接修补 CallLater.funcs,这有点尴尬,因为您必须手动将 log_b 替换为 log_c - 但在这里:

import logging
from unittest import mock

from mymodule import log_c
from mydecorator import CallLater

logging.basicConfig(level=logging.INFO)


def test_mocking(caplog):
    funcs = [log_c if f.__name__ == 'log_b' else f for f in CallLater.funcs]
    with mock.patch.object(CallLater, 'funcs', funcs):
        CallLater.call_now()

        logs = [rec.message for rec in caplog.records]
        assert logs == ["A", "C"]

请注意,您不能直接与函数进行比较(例如f == log_b),因为log_b 是修饰函数,而不是保存在CallLater.funcs 中的函数。

【讨论】:

这很有意义。我没有考虑何时创建函数列表。感谢您快速明确的回复! 很高兴能帮上忙!

以上是关于如何模拟未按名称调用的函数?的主要内容,如果未能解决你的问题,请参考以下文章

如何在函数中模拟函数。是不是可以使用两个或多个补丁来模拟函数中的函数调用?

具有相同命名空间名称时的模拟库函数

C - 语句未按顺序执行

当我无法将函数本身设为静态时如何调用非静态函数

如何在 Sinon 中模拟链式函数调用

如何使用 jest 模拟链式函数调用?