概要
pytest-freezegunの使い方を紹介する
目的
pytest-freezegunの基本的な使い方を知ることができる
使用技術
python = 3.11.2
Django = 4.2.3
djangorestframework = 3.14.0
pytest = 7.3.1
factory-boy = 3.2.1
pytest-freezegun = 0.4.2
テストの概要・APIの仕様について
今回はログアウトAPIを使用します
ログインすると1時間のセッションが保持されるはずですが、
本当に1時間でセッションの有効期限が切れるのかをテストします
セッションが切れていればログアウトAPIを叩いた時認証エラーとなります
※ログインにはclient.login()
(返り値がbool)を使用しているので判定にはuser.is_authenticated
を使用せず、ログアウトAPIを使用しています
早速みてみましょう
まずはライブラリをインストールしましょう
$ pip install pytest-freezegun
使用するフィクスチャを定義します
conftest.py
import pytest
from datetime import datetime, timedelta
from django.core.management import call_command
from django.contrib.auth.models import Group
from rest_framework.test import APIClient
from app.models import Employee, User
from app.tests.factories.employee import EmployeeFactory
from app.tests.factories.user import UserFactory
@pytest.fixture(scope="session")
def django_db_setup(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
call_command("loaddata", "test.json")
@pytest.fixture
def admin_user():
"""admin権限ユーザーを作成"""
group = Group.objects.get(name="admin")
user = UserFactory(groups_id=group.id)
return EmployeeFactory(user=user)
@pytest.fixture
def client(db):
"""APIクライアントを作成"""
return APIClient()
@pytest.fixture
def login_user(client):
"""権限を指定してログインを実施"""
def _method(employee):
client.login(
id=employee.id,
password="password",
)
return _method
@pytest.fixture
def get_logout_url():
"""ログアウトAPIのURL"""
return "/api/logout/"
@pytest.fixture
def freeze_time(freezer):
"""指定した時間まで経過させる"""
def _method(hours, minutes, seconds):
mock_now = datetime.now() + timedelta(
hours=int(hours), minutes=int(minutes), seconds=int(seconds)
)
freezer.move_to(mock_now)
return _method
@pytest.fixture
def unauthorized_message():
"""認証情報が含まれていない場合のエラーメッセージ"""
return {"detail": "認証情報が含まれていません。"}
@pytest.fixture
def logout_message():
"""ログアウトが成功した場合のメッセージ"""
return {"detail": "ログアウトしました"}
実際のテストコードです
59分59秒経過後はまだセッションが保持されているので、エラーとなりません
test_logout.py
import pytest
from rest_framework import status
@pytest.mark.django_db
def test_admin_user_does_not_auto_logout_near_expiry(
client, login_user, admin_user, get_logout_url, freeze_time, logout_message
):
"""admin権限ユーザーがログインして59分59秒経過後、自動的にログアウトされないか"""
login_user(admin_user)
freeze_time(0, 59, 59)
response = client.post(get_logout_url, format="json")
assert response.status_code == status.HTTP_200_OK
assert response.json() == logout_message
しかし、1時間経過後にログアウトAPIを実行すると認証エラーとなります(セッションが切れている)
logout.py
@pytest.mark.django_db
def test_admin_user_logout_session_expired(
client, login_user, admin_user, get_logout_url, freeze_time, unauthorized_message
):
"""admin権限ユーザーがログインして1時間経過後、自動的にログアウトされるか"""
login_user(admin_user)
freeze_time(1, 0, 0)
response = client.post(get_logout_url, format="json")
assert response.status_code == status.HTTP_401_UNAUTHORIZED
assert response.json() == unauthorized_message
freezegunを使用したフィクスチャについて
conftest.py
@pytest.fixture
def freeze_time(freezer):
"""指定した時間まで経過させる"""
def _method(hours, minutes, seconds):
mock_now = datetime.now() + timedelta(
hours=int(hours), minutes=int(minutes), seconds=int(seconds)
)
freezer.move_to(mock_now)
return _method
freezer
はフィクスチャとしてすでに暗黙的に用意されています
def freeze_time
の中にdef _method
として定義することでフィクスチャを利用する際に引数を渡すことができます
使用例
freeze_time(0, 59, 59)
# 0時間59分59秒
freeze_time(1, 0, 0)
# 1時間0分0秒