在内部函数中模拟默认参数值

Mock default parameter value in inner function

我有以下(简化的)代码:

def get_redis()
  return redis_instance


def bar(key, value, redis=get_redis())
  redis.set(key, value)


def foo()
  bar("key", value)

在我的测试中,我想将函数 get_redis 模拟为 return fakeredis.FakeStrictRedis() 的一个实例,所以我这样做了

def test_foo(mocker):
  mocker.patch("app.main.get_redis", return_value=fakeredis.FakeStrictRedis())
  foo()

mock 函数无效,foo 函数尝试使用来自 main 的 get_redis 函数连接到真正的 redis。

如果我这样写就可以了

def bar(key, value)
  redis=get_redis()
  redis.set(key, value)

这可行,但我可以将 redis 作为默认值传递。我该如何嘲笑?

我将按如下方式稍微修改 bar 函数,这样您的函数就不会在应用模拟之前被调用:

def bar(key, value, redis=get_redis)
    if callable(redis):
        redis = redis()
    redis.set(key, value)

以这种方式编写函数意味着您的模拟将在函数被调用时应用,而不是在启动时应用任何模拟之前。简而言之,以这种方式编写 bar,确保 get_redis 的 return 每次调用函数时都会通过您的 bar 函数传播。

您的模拟没有任何问题,但您似乎误解了默认参数值的工作原理。如 the Python Language Reference 中所述, 执行函数 定义 时会计算默认参数值

在您的情况下,这意味着在定义 bar 时已经调用了原始 get_redis

def bar(key, value, redis=get_redis()):

此语句在 pytest 导入您的模块时执行,即在执行 test_foo 之前执行,因此在测试中模拟 get_redis 没有效果,因为到那时已经太晚了.

要使 default-supplying 工厂函数可模拟,请使用 None 作为默认值并使函数调用工厂函数,除非在调用中指定了另一个值:

def bar(key, value, redis=None)
  redis = redis or get_redis()
  redis.set(key, value)