【问题标题】:Mocking a module level function in pytest在 pytest 中模拟模块级函数
【发布时间】:2019-11-07 20:42:09
【问题描述】:

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

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'

其他.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() 的调用?

【问题讨论】:

    标签: python module mocking pytest decorator


    【解决方案1】:

    在您的测试函数中将from example import my_func 移动到with 中。还要在它真正来自的地方修补它,other.get_value。可能就这些了。


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

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

    一种更可靠的方法是将原始导入的模块对象保存在其他位置,从sys.modules 中删除,在测试期间使用修补版本重新导入,然后在之后将原始导入放回sys.modules考试。您可以通过在 sys.modules 上的 patch.dict() 上下文中导入来做到这一点。

    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(),那么原始函数应该可以作为__wrapped__ 属性使用。这可能不可用,具体取决于装饰器的实现方式。

    【讨论】:

      猜你喜欢
      • 2020-02-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-24
      • 2018-12-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多