Flask、flask_login、pytest:如何设置 flask_login 的 current_user?

Flask, flask_login, pytest: How do I set flask_login's current_user?

我正在尝试使用 pytest 对我的 Flask 应用程序进行单元测试。我有以下端点测试用例,需要来自 flask_logincurrent_user:

的信息
def test_approval_logic():
    with app.test_client() as test_client:
        app_url_put = '/requests/process/2222'

        with app.app_context():
            user = User.query.filter_by(uid='xxxxxxx').first()
            with app.test_request_context():
                login_user(user)
                user.authenticated = True
                db.session.add(user)

                data = dict(
                    state='EXAMPLE_STATE_NAME',
                    action='approve'
                )
                resp = test_client.put(app_url_put, data=data)
                assert resp.status_code == 200

test_request_context 中,我可以正确设置 current_user。但是,这个测试失败了,因为在处理PUT的requests视图中,没有登录用户和500错误结果。错误消息是 AttributeError: 'AnonymousUserMixin' object has no attribute 'email'。谁能解释为什么 current_user 消失了,我该如何正确设置它?

以下是我在我的网站上的做法:

user = User.query.filter_by(user_id='xxxxxxx').one_or_none()
if user:
    user.authenticated = True
    db.session.add(user)
    db.session.commit()
    login_user(user)
else:
   # here I redirect to an unauthorized page, as the user wasn't found

我不知道顺序是问题还是只是缺少 db.session.commit(),但我认为您需要同时完成这两项操作才能使您的看跌请求生效。

另请注意,我使用的是 one_or_none(),因为不应该存在多个用户使用相同 user_id 的可能性,只是一个 True 或 False,具体取决于用户是否是否找到。

使用测试客户端发送请求

当前的session没有绑定到test_client,所以请求使用了一个新的session。

在客户端设置会话 cookie,以便 Flask 可以为请求加载相同的会话:

from flask import session

def set_session_cookie(client):
    val = app.session_interface.get_signing_serializer(app).dumps(dict(session))
    client.set_cookie('localhost', app.session_cookie_name, val)

用法:

# with app.test_client() as test_client:                            # Change these
#     with app.app_context():                                       #
#         with app.test_request_context():                          #
with app.test_request_context(), app.test_client() as test_client:  # to this
    login_user(user)
    user.authenticated = True
    db.session.add(user)

    data = dict(
        state='EXAMPLE_STATE_NAME',
        action='approve'
    )
    set_session_cookie(test_client)  # Add this
    resp = test_client.put(app_url_put, data=data)

关于with app.test_request_context()

的兼容性

我。 with app.test_client()

with app.test_client() 保留了请求的上下文(Flask 文档:Keeping the Context Around),因此在退出内部 with app.test_request_context():

时会出现此错误

AssertionError: Popped wrong request context. (<RequestContext 'http://localhost/requests/process/2222' [PUT] of app> instead of <RequestContext 'http://localhost/' [GET] of app>)

而是在 app.test_client() 之前输入 app.test_request_context(),如上所示。

二。 with app.app_context()

with app.test_request_context() 已经推送了一个应用上下文,所以 with app.app_context() 是不必要的。

在不分派请求的情况下使用测试请求上下文

来自 https://flask.palletsprojects.com/en/2.0.x/api/#flask.Flask.test_request_context:

This is mostly useful during testing, where you may want to run a function that uses request data without dispatching a full request.

用法:

data = dict(
    state='EXAMPLE_STATE_NAME',
    action='approve'
)
with app.test_request_context(data=data):  # Pass data here
    login_user(user)
    user.authenticated = True
    db.session.add(user)

    requests_process(2222)  # Call function for '/requests/process/2222' directly

我的猜测是您的 PUT 请求中没有传递任何会话 cookie。

这是我在测试期间如何记录用户的示例(我个人使用 unittest 而不是 pytest,所以我减少了代码严格到最低限度,但如果你想要一个完整的例子,请告诉我 unittest)

from whereyourappisdefined import application
from models import User
from flask_login import login_user

# Specific route to log an user during tests
@application.route('/auto_login/<user_id>')
def auto_login(user_id):
    user = User.query.filter(User.id == user_id).first()
    login_user(user)
    return "ok"

def yourtest():
    application.config['TESTING'] = True # see my side note
    test_client = application.test_client()
    response = test_client.get(f"/auto_login/1")

    app_url_put = '/requests/process/2222'
    data = dict(
        state='EXAMPLE_STATE_NAME',
        action='approve'
    )
    r = test_client.put(app_url_put, data=data)
    

在文档中我们可以读到: https://werkzeug.palletsprojects.com/en/2.0.x/test/#werkzeug.test.Client

The use_cookies parameter indicates whether cookies should be stored and sent for subsequent requests. This is True by default but passing False will disable this behavior.

因此在第一个请求 GET /auto_login/1 期间,应用程序将接收一个会话 cookie 并保留它以用于进一步的 HTTP 请求。

旁注:

During setup, the TESTING config flag is activated. What this does is disable error catching during request handling so that you get better error reports when performing test requests against the application.