LoginSignup
1
1

[Django REST Framework] pytest-freezegun🥶を使用して意図的に時間を経過させよう

Last updated at Posted at 2023-09-29

概要

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秒 

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1