模拟在 class 变量初始化期间作为参数传入的函数

Mocking a function that is passed in as a parameter during a class variable initialization

scuevals_api/resources/students.py:

def year_in_range(year):
    return datetime.now().year <= year <= datetime.now().year + 10


class StudentsResource(Resource):
    args = {
        'graduation_year': fields.Int(required=True, validate=year_in_range),
    }

    ...

我正在尝试模拟 year_in_range(总是 return 正确)但是,到目前为止我的所有尝试都失败了。

我在 mock.patch 中使用装饰器方法并尝试了很多不同的目标,但我认为应该是正确的目标是: @mock.patch('scuevals_api.resources.students.year_in_range', return_value=True)

模拟函数从未被调用,因为它没有正确模拟。我也没有收到任何错误。

我唯一剩下的怀疑是它与函数作为参数传递给 fields.Int 有关(因此问题标题),但在我看来,它不应该影响任何东西.

我不知道应该在哪里模拟这个函数?

等到 mock 打好 year_in_range 补丁时,为时已晚。 mock.patch 导入由您提供的字符串指定的模块并修补模块中指示的名称,以便它引用模拟对象 - 它不会从根本上改变函数对象本身。在导入 scuevals_api.resources.students 时,将执行 StudentsResource class 的主体,并在 StudentResource.args['graduation_year'] 对象中保存对原始 year_in_range 的引用,结果使引用模拟对象的名称 year_in_range 没有影响。

在这种特殊情况下,您有几种选择:

  1. 假设您正在尝试测试某些功能,而不是尝试模拟 year_in_range 您可以将测试条件的数据植入数据库 (?)
  2. 您可以修补 datetime.now,它将被 year_in_range
  3. 调用
  4. 您可以修补 StudentResource.args['graduation_year'] 的成员,其中已保存传递给 validate 的函数。

感谢 Chris Hunt 的解释,我想出了一个替代解决方案。它确实修改了应用程序代码而不是测试代码,但如果这是可以接受的(在今天这个时代可能应该是这样,因为拥有可测试的代码是高优先级的),这是一个非常简单的解决方案:

无法模拟 year_in_range,因为在模拟完成之前保存了对原始函数的引用。因此,"wrap" 你想用另一个函数模拟的函数,而不是传递包装器。可以使用 lambda 函数以一种漂亮而整洁的方式完成包装:

def year_in_range(year):
    return datetime.now().year <= year <= datetime.now().year + 10


class StudentsResource(Resource):
    args = {
        'graduation_year': fields.Int(required=True, validate=lambda y: year_in_range(y)),
    }

    ...

现在,当我按照问题中所述模拟 year_in_range 时,它会起作用。原因是因为现在将引用保存到 lambda 函数,而不是原始 year_in_range(在 lambda 函数运行之前不会访问,这将在测试期间进行)。