在pytest中模拟模块级别的功能

时间:2019-06-26 00:42:22

标签: python module mocking pytest decorator

我有一个带有装饰器的函数。装饰器接受参数,并且该参数的值是从另一个函数调用派生的。

example.py

from cachetools import cached
from cachetools import TTLCache

from other import get_value


@cached(cache=TTLCache(maxsize=1, ttl=get_value('cache_ttl')))
def my_func():
    return 'result'

other.py

def get_value(key):
    data = {
        'cache_ttl': 10,
    }
    # Let's assume here we launch a shuttle to the space too.
    return data[key]

我想模拟对get_value()的呼叫。我在测试中使用以下内容:

example_test.py

import mock
import pytest

from example import my_func


@pytest.fixture
def mock_get_value():
    with mock.patch(
        "example.get_value",
        autospec=True,
    ) as _mock:
        yield _mock


def test_my_func(mock_get_value):
    assert my_func() == 'result'

在这里,我将mock_get_value注入test_my_func。但是,由于我的装饰器是在第一次导入时调用的,因此get_value()被立即调用。知道是否有一种方法可以在使用pytest立即导入模块之前模拟对get_value()的调用吗?

1 个答案:

答案 0 :(得分:1)

在测试功能的from example import my_func内部移动with。还要修补它真正来自的地方other.get_value。这可能就是全部。


Python在sys.modules中缓存模块,因此模块级代码(如函数定义)仅在 first anywhere 的导入中运行。如果这不是第一次,您可以使用importlib.reload()或通过删除sys.modules中的相应密钥并重新导入来强制重新导入。

请注意,重新导入模块可能会产生副作用,并且您可能还希望在运行测试后再次重新导入模块,以免干扰其他测试。如果另一个模块正在使用在重新导入的模块中定义的对象,则这些对象不仅会消失,而且可能无法按照预期的方式进行更新。例如,重新导入模块可能会创建应该是单例的第二个实例。

另一种健壮的方法是将原始导入的模块对象保存在其他位置,从sys.modules中删除,在测试期间使用修补版本重新导入,然后将原始导入放回{{ 1}}。您可以在sys.modules上的patch.dict()上下文中进行导入。

sys.modules

另一种可能性是在测试中自己调用原始函数上的装饰器。 如果装饰器使用了import mock import sys import pytest @pytest.fixture def mock_get_value(): with mock.patch( "other.get_value", autospec=True, ) as _mock, mock.patch.dict("sys.modules"): sys.modules.pop("example", None) yield _mock def test_my_func(mock_get_value): from example import my_func assert my_func() == 'result' / functools.wraps(),那么原始功能应该作为functools.update_wrapper()属性可用。可能无法使用,具体取决于装饰器的实现方式。