模拟在 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
没有影响。
在这种特殊情况下,您有几种选择:
- 假设您正在尝试测试某些功能,而不是尝试模拟
year_in_range
您可以将测试条件的数据植入数据库 (?)
- 您可以修补
datetime.now
,它将被 year_in_range
调用
- 您可以修补
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 函数运行之前不会访问,这将在测试期间进行)。
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
没有影响。
在这种特殊情况下,您有几种选择:
- 假设您正在尝试测试某些功能,而不是尝试模拟
year_in_range
您可以将测试条件的数据植入数据库 (?) - 您可以修补
datetime.now
,它将被year_in_range
调用
- 您可以修补
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 函数运行之前不会访问,这将在测试期间进行)。