在Flask单元测试中重写flask.g
我正在为我的Flask应用写一些单元测试,这个应用使用OpenID进行身份验证。因为似乎没有办法通过OpenID让测试客户端登录(我问过这个问题,但还没有人回答:Flask OpenID unittest),所以我想在测试中覆盖g.user的值,于是我试了试http://flask.pocoo.org/docs/testing/#faking-resources-and-context上的代码片段,结果如我所愿地工作了。
不幸的是,当使用flask-login时,g.user在before_request这个包装器中被覆盖,这个包装器会设置
g.user = current_user
current_user为匿名用户,所以我的测试用例就坏掉了。一个解决办法是只在测试模式下执行before_request的代码,但在生产代码中添加特定于测试的逻辑感觉很糟糕。我也尝试过搞定请求上下文,但g.user最终还是会被覆盖。有没有什么干净的办法来解决这个问题?
2 个回答
根据另一个问题在Flask单元测试中,如何模拟请求全局的 `g` 对象上的对象?,我找到了一种方法:
在我的应用中,我把登录逻辑从 `before_request` 中提取出来,放到了一个单独的函数里。然后,在我的测试中,我对这个函数进行了“补丁”,让它返回我想用的特定用户,这样我就可以在多个测试中使用。虽然 `before_request` 仍然会在测试中运行,但通过补丁这个函数,我就可以避免实际的登录过程。
我不确定这是不是最干净的做法,但我觉得比在 `before_request` 中添加仅用于测试的逻辑要好,这只是一个重构。
官方文档中有一个关于“伪造资源和上下文”的例子,具体可以查看这里:
首先,你需要确保只有在g.user
还不存在的情况下才设置它。你可以使用getattr
来做到这一点。下面是一个稍微修改过的例子:
@APP.before_request
def set_user():
user = getattr(g, 'user', None)
if user is None:
g.user = concrete_implementation()
return user
通过使用getattr
,我们可以在测试时“注入”一些东西。如果不这样做,单元测试注入的值会被具体的实现覆盖掉。
接下来,我们要做的是监听appcontext_pushed
事件,并用一个可以测试的值来设置g.user
的值。这是在before_request
钩子之前发生的。所以在调用before_request
时,getattr
会返回我们的测试值,而跳过具体的实现:
from contextlib import contextmanager
from flask import appcontext_pushed, g
@contextmanager
def user_set(app, user):
def handler(sender, **kwargs):
g.user = user
with appcontext_pushed.connected_to(handler, app):
yield
有了这个小工具,我们可以在需要使用可测试的值时随时注入一些东西:
def test_user_me():
with user_set(app, 'our-testable-user'):
c = app.test_client()
resp = c.get('/protected-resource')
assert resp.data == '...'