私は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 束ですが、この特定の単体テストへの取り組み方をまだ理解していません。
問題は、test_client.get()
を呼び出すと新しいリクエストコンテキストがプッシュされるため、テストケースのsetUp()
メソッドでプッシュしたものが/user
ハンドラが見ます。
ドキュメントの ログインとログアウト および テストメッセージの追加 セクションに示されているアプローチは、ログインをテストするための最良のアプローチだと思います。アイデアは、通常のクライアントのように、アプリケーションを介してログイン要求を送信することです。これにより、ログインしたユーザーがテストクライアントのユーザーセッションに登録されます。
問題は異なるリクエストコンテキストです。
通常の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')
主にユニットテストファイルにパスワードを保存する必要があるため(そして私は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"
ただし、私はflaskアプリのテストインスタンスにかなりの多くの変更を加えています。たとえば、別のDBを使用してそれを構築しているため、ルートを追加してもコードが目立たなくなりますこのルートは実際のアプリには存在しません。
それから私はします:
def login(self):
response = self.app.test_client.get("/auto_login")
その後test_client
はログインする必要があります。
ドキュメントから: https://flask-login.readthedocs.io/en/latest/
単体テスト時に認証をグローバルにオフにすると便利です。これを有効にするために、アプリケーション構成変数LOGIN_DISABLEDがTrueに設定されている場合、このデコレーターは無視されます。