使用Pytest测试集成Flask-WTForms的Flask应用时,POST请求因缺少CSRF令牌验证失败的解决方法
Pytest测试集成Flask-WTForms的Flask应用时,POST请求因缺少CSRF令牌验证失败的解决方法
嗨,我完全懂你遇到的问题——把应用改成用Flask-WTForms后,测试里的POST请求因为没带CSRF令牌直接触发了400错误,之前找到的旧方案还因为Werkzeug版本更新失效了。我给你分享两种可行的解决办法,优先推荐第一种,因为它更贴近真实用户的使用场景。
问题根源
Flask-WTForms默认开启了CSRF保护机制,所有提交的表单必须携带有效的CSRF令牌才能通过验证。你的测试直接提交表单数据,没有包含这个令牌,导致表单验证失败,服务器返回400错误,自然也就没有你预期的重定向Location头,才会抛出BadRequestKeyError。
解决方案一:模拟真实流程获取并携带CSRF令牌(推荐)
我们可以先发送一个GET请求到表单页面,从客户端会话中提取CSRF令牌,再把它加入POST的表单数据里,完美复刻用户的真实操作流程。
修改你的tests/test_auth.py里的test_register函数:
import pytest from flask import g, session from flaskr.db import get_db def test_register(client, app): # 先GET请求注册页面,触发CSRF令牌的生成 get_response = client.get('/auth/register') assert get_response.status_code == 200 # 从客户端会话中提取CSRF令牌 with client.session_transaction() as sess: csrf_token = sess['csrf_token'] # 发送POST请求,带上用户名、密码和CSRF令牌 response = client.post( '/auth/register', data={'username': 'a', 'password': 'a', 'csrf_token': csrf_token}, follow_redirects=True ) # 验证请求成功后跳转到登录页 assert response.request.path == '/auth/login' # 验证用户确实被创建 with app.app_context(): assert get_db().execute( "SELECT * FROM user WHERE username = 'a'", ).fetchone() is not None
这个方法的核心是用client.session_transaction()上下文管理器访问客户端的会话,Flask-WTF会把生成的CSRF令牌存在会话的csrf_token键里,我们直接取出来用就行。
解决方案二:测试环境临时禁用CSRF保护(快速测试用)
如果你只是想快速跳过CSRF验证完成测试(不推荐长期使用,因为会漏掉CSRF相关的测试覆盖),可以在测试的应用配置里禁用CSRF保护。
修改tests/conftest.py里的app fixture:
@pytest.fixture def app(): db_fd, db_path = tempfile.mkstemp() app = create_app({ 'TESTING': True, 'DATABASE': db_path, 'WTF_CSRF_ENABLED': False # 临时关闭CSRF保护 }) with app.app_context(): init_db() get_db().executescript(_data_sql) yield app os.close(db_fd) os.unlink(db_path)
这样你的测试POST请求就不需要带CSRF令牌了,但记住这只是权宜之计,生产环境还是要保持CSRF保护开启的,所以尽量用第一种方案哦。
备注:内容来源于stack exchange,提问作者jaybee
相关产品推荐
相关产品推荐

