测试 Flask 登录和身份验证?

Posted

技术标签:

【中文标题】测试 Flask 登录和身份验证?【英文标题】:Testing Flask login and authentication? 【发布时间】:2014-02-25 16:44:30 【问题描述】:

我正在开发一个 Flask 应用程序并使用 Flask-security 进行用户身份验证(这反过来又在下面使用 Flask-login)。

我有一条需要身份验证的路由/user。我正在尝试编写一个单元测试来测试,对于经过身份验证的用户,这将返回适当的响应。

在我的单元测试中,我正在创建一个用户并以该用户身份登录,如下所示:

from unittest import TestCase
from app import app, db
from models import User
from flask_security.utils import login_user

class UserTest(TestCase):
   def setUp(self):
       self.app = app
       self.client = self.app.test_client()
       self._ctx = self.app.test_request_context()
       self._ctx.push()

       db.create_all()

   def tearDown(self):
       if self._ctx is not None:
           self._ctx.pop()

       db.session.remove()
       db.drop_all()

   def test_user_authentication():
       # (the test case is within a test request context)
       user = User(active=True)
       db.session.add(user)
       db.session.commit()
       login_user(user)

       # current_user here is the user
       print(current_user)

       # current_user within this request is an anonymous user
       r = test_client.get('/user')

在测试中current_user 返回正确的用户。但是,请求的视图始终返回 AnonymousUser 作为 current_user

/user 路由定义为:

class CurrentUser(Resource):
    def get(self):
        return current_user  # returns an AnonymousUser

我很确定我只是没有完全理解测试 Flask 请求上下文的工作原理。我已经阅读了这个 Flask Request Context documentation 一堆,但我仍然不明白如何处理这个特定的单元测试。

【问题讨论】:

如果你真的展示了你实际在做什么,那会有所帮助 - ***.com/help/mcve 已更新以显示测试的设置方式。 【参考方案1】:

问题在于test_client.get() 调用会导致一个新的请求上下文被推送,因此您在测试用例的setUp() 方法中推送的那个不是/user 处理程序看到的那个。

我认为文档的Logging In and Out 和Test Adding Messages 部分中显示的方法是测试登录的最佳方法。这个想法是通过应用程序发送登录请求,就像普通客户端一样。这将负责在测试客户端的用户会话中注册登录用户。

【讨论】:

【参考方案2】:

我不太喜欢显示的其他解决方案,主要是因为您必须将密码保存在单元测试文件中(而且我使用的是 Flask-LDAP-Login,因此添加虚拟用户等并不明显。 ),所以我绕过它:

在我设置测试应用的地方,我添加了:

@app.route('/auto_login')
def auto_login():
    user = ( models.User
             .query
             .filter_by(username="Test User")
             .first() )
    login_user(user, remember=True)
    return "ok"

但是,我对烧瓶应用程序的测试实例进行了很多更改,例如使用不同的数据库来构建它,因此添加路由不会使代码明显混乱。实际应用中不存在这条路线。

然后我做:

def login(self):
    response = self.app.test_client.get("/auto_login")

之后使用test_client 完成的任何操作都应登录。

【讨论】:

无论我尝试什么,current_user 始终是 AnonymousUser。知道如何解决这种行为吗? 我的猜测是您没有使用test_client,或者您在request_context 或类似方面有其他问题。在测试中你必须非常小心这些 - 确保你了解请求上下文是什么,并知道你在哪个上下文中。 感谢您的回答。我通过将我的测试代码包装在 with self.client: 中让它工作 我正在使用 OpenID 登录,这是我在测试时可以使用的唯一方法。谢谢。【参考方案3】:

问题在于不同的请求上下文。

在您的普通 Flask 应用程序中,每个请求都会创建一个新的上下文,该上下文将在整个链中重复使用,直到创建最终响应并将其发送回浏览器。

当您创建和运行 Flask 测试并执行请求(例如 self.client.post(...))时,上下文会在收到响应后被丢弃。因此,current_user 始终是AnonymousUser

要解决这个问题,我们必须告诉 Flask 在整个测试中重用相同的上下文。你可以通过简单地包装你的代码来做到这一点:

with self.client:

您可以在以下精彩文章中阅读有关此主题的更多信息: https://realpython.com/blog/python/python-web-applications-with-flask-part-iii/

示例

之前:

def test_that_something_works():
    response = self.client.post('login',  username: 'James', password: '007' )

    # this will fail, because current_user is an AnonymousUser
    assertEquals(current_user.username, 'James')

之后:

def test_that_something_works():
    with self.client:
        response = self.client.post('login',  username: 'James', password: '007' )

        # success
        assertEquals(current_user.username, 'James')

【讨论】:

最佳答案,拯救了我的一天!【参考方案4】:

来自文档:https://flask-login.readthedocs.io/en/latest/

单元测试时可以方便的全局关闭认证。要启用此功能,如果应用程序配置变量 LOGIN_DISABLED 设置为 True,则此装饰器将被忽略。

【讨论】:

以上是关于测试 Flask 登录和身份验证?的主要内容,如果未能解决你的问题,请参考以下文章

Flask - 如果当前用户未通过身份验证,如何重定向到登录页面?

Flask 中 AJAX 身份验证的 CSRF 保护

Flask-Mail 未使用 Gmail 和应用密码进行身份验证

使用 flask_pymongo 时身份验证失败

在 OCUnit 应用程序测试中绕过登录/身份验证

如何测试使用 JWT 身份验证的节点 API(使用用户登录获取令牌)