模拟__call__属性不会模拟该函数

时间:2019-09-06 13:17:30

标签: python unit-testing mocking python-unittest

我正在尝试通过以下方式模拟函数:

def foo(x):
    return 10 * x

from unittest.mock import patch

with patch.object(foo, "__call__", lambda x: 100 * x):
    print(foo(5))
    print(foo.__call__(5))

但是,输出如下:

50
500

代替

500
500

也就是说,foo(5)不使用模拟的__call__属性。我假设像foo(5)这样的函数调用总是会转换为foo.__call__(5)。显然不是这样,但我不知道为什么。可以用patch.object模拟该功能吗?

我知道我可以用patch("path.to.foo", lambda x: 100*x)对其进行修补,但是我希望可以对对象进行修补而无需知道路径。但是也许我只需要做类似的事情:

patch(foo.__module__+'.'+foo.__name__, lambda x: 100*x)

但是我不确定这是否是修补的好方法。对我来说,这看起来像是在自找麻烦,但我不确定那是什么麻烦。

那么,我该如何修补功能对象?

1 个答案:

答案 0 :(得分:0)

foo(5)转换为type(foo).__call__(foo, 5)而不是foo.__call__(5)

因此,需要修改类的__call__方法,而不是实例。为了不弄乱同一个类的其他实例,应该从一个唯一的类创建该实例,然后可以自由地对该类进行修补。

我找不到改变函数对象类的方法,因此我为函数创建了一个简单的包装器,将其转换为唯一类的实例,同时仍具有与原始函数相似的功能。然后,可以修补该类的__call__方法:

from unittest import mock

def mockable_function(f):

    class Function():

        def __call__(self, *args, **kwargs):
            return f(*args, **kwargs)

    return Function()

@mockable_function
def foo(x):
    return 10 * x

with mock.patch.object(type(foo), "__call__", lambda _, x: 100 * x):
    print(foo(5))
    print(foo.__call__(5))

这将输出:

500
500

也许不是很漂亮,但是至少有一种编写可以用mock.patch.object进行修改的函数的方法。

根据我的测试,使用mock.patch("some.module.foo", ...)的问题在于,如果某些模块已将foo导入为from some.module import foo,则foo不会受到影响,而mock.patch.object(foo, ...)已正确修补。