test.py

テスト

テストのないコードは、設計どおりに壊れています。

Jacob Kaplan-Moss

REST frameworkには、Djangoの既存のテストフレームワークを拡張し、APIリクエストの作成のサポートを向上させるいくつかのヘルパークラスが含まれています。

APIRequestFactory

Djangoの既存のRequestFactoryクラスを拡張します。

テストリクエストの作成

APIRequestFactoryクラスは、Djangoの標準のRequestFactoryクラスとほぼ同じAPIをサポートしています。これは、標準の.get().post().put().patch().delete().head()、および.options()メソッドがすべて利用可能であることを意味します。

from rest_framework.test import APIRequestFactory

# Using the standard RequestFactory API to create a form POST request
factory = APIRequestFactory()
request = factory.post('/notes/', {'title': 'new idea'})

format引数の使用

postputpatchなど、リクエストボディを作成するメソッドには、マルチパートフォームデータ以外のコンテンツタイプを使用してリクエストを簡単に生成できるようにするformat引数が含まれています。例:

# Create a JSON POST request
factory = APIRequestFactory()
request = factory.post('/notes/', {'title': 'new idea'}, format='json')

デフォルトでは、利用可能なフォーマットは'multipart''json'です。Djangoの既存のRequestFactoryとの互換性のため、デフォルトのフォーマットは'multipart'です。

より広範なリクエストフォーマットをサポートするか、デフォルトのフォーマットを変更するには、設定セクションを参照してください

リクエストボディの明示的なエンコード

リクエストボディを明示的にエンコードする必要がある場合は、content_typeフラグを設定することでエンコードできます。例:

request = factory.post('/notes/', json.dumps({'title': 'new idea'}), content_type='application/json')

フォームデータを使用したPUTおよびPATCH

DjangoのRequestFactoryとREST frameworkのAPIRequestFactoryの間の注目すべき1つの違いは、マルチパートフォームデータが.post()以外のメソッドに対してもエンコードされることです。

たとえば、APIRequestFactoryを使用すると、次のようにフォームPUTリクエストを作成できます。

factory = APIRequestFactory()
request = factory.put('/notes/547/', {'title': 'remember to email dave'})

DjangoのRequestFactoryを使用すると、データを明示的に自分でエンコードする必要があります。

from django.test.client import encode_multipart, RequestFactory

factory = RequestFactory()
data = {'title': 'remember to email dave'}
content = encode_multipart('BoUnDaRyStRiNg', data)
content_type = 'multipart/form-data; boundary=BoUnDaRyStRiNg'
request = factory.put('/notes/547/', content, content_type=content_type)

認証の強制

リクエストファクトリを使用してビューを直接テストする場合、正しい認証資格情報を構築するのではなく、リクエストを直接認証できると便利なことがよくあります。

リクエストを強制的に認証するには、force_authenticate()メソッドを使用します。

from rest_framework.test import force_authenticate

factory = APIRequestFactory()
user = User.objects.get(username='olivia')
view = AccountDetail.as_view()

# Make an authenticated request to the view...
request = factory.get('/accounts/django-superstars/')
force_authenticate(request, user=user)
response = view(request)

メソッドのシグネチャはforce_authenticate(request, user=None, token=None)です。呼び出しを行うとき、ユーザーとトークンのいずれかまたは両方を設定できます。

たとえば、トークンを使用して強制的に認証する場合、次のようなことを行うことができます。

user = User.objects.get(username='olivia')
request = factory.get('/accounts/django-superstars/')
force_authenticate(request, user=user, token=user.auth_token)

注意: force_authenticateは、request.userをインメモリのuserインスタンスに直接設定します。保存されたuserの状態を更新する複数のテストで同じuserインスタンスを再利用する場合は、テスト間でrefresh_from_db()を呼び出す必要がある場合があります。


注意: APIRequestFactoryを使用する場合、返されるオブジェクトはDjangoの標準のHttpRequestであり、REST frameworkのRequestオブジェクトではありません。Requestオブジェクトは、ビューが呼び出された後にのみ生成されます。

これは、リクエストオブジェクトに属性を直接設定しても、常に期待どおりの効果が得られない可能性があることを意味します。たとえば、.tokenを直接設定しても効果はなく、.userを直接設定しても、セッション認証が使用されている場合にのみ機能します。

# Request will only authenticate if `SessionAuthentication` is in use.
request = factory.get('/accounts/django-superstars/')
request.user = user
response = view(request)

CSRF検証の強制

デフォルトでは、APIRequestFactoryで作成されたリクエストは、REST frameworkビューに渡されるときにCSRF検証が適用されません。CSRF検証を明示的にオンにする必要がある場合は、ファクトリをインスタンス化するときにenforce_csrf_checksフラグを設定することで有効にできます。

factory = APIRequestFactory(enforce_csrf_checks=True)

注意: Djangoの標準のRequestFactoryにはこのオプションを含める必要がないことに注意してください。これは、通常のDjangoを使用する場合、CSRF検証はミドルウェアで実行され、ビューを直接テストするときには実行されないためです。REST frameworkを使用する場合、CSRF検証はビュー内で実行されるため、リクエストファクトリはビューレベルのCSRFチェックを無効にする必要があります。


APIClient

Djangoの既存のClientクラスを拡張します。

リクエストの作成

APIClientクラスは、Djangoの標準のClientクラスと同じリクエストインターフェースをサポートしています。これは、標準の.get().post().put().patch().delete().head()、および.options()メソッドがすべて利用可能であることを意味します。例:

from rest_framework.test import APIClient

client = APIClient()
client.post('/notes/', {'title': 'new idea'}, format='json')

より広範なリクエストフォーマットをサポートするか、デフォルトのフォーマットを変更するには、設定セクションを参照してください

認証

.login(**kwargs)

loginメソッドは、Djangoの通常のClientクラスと同じように機能します。これにより、SessionAuthenticationを含むビューに対するリクエストを認証できます。

# Make all requests in the context of a logged in session.
client = APIClient()
client.login(username='lauren', password='secret')

ログアウトするには、通常どおりlogoutメソッドを呼び出します。

# Log out
client.logout()

loginメソッドは、セッション認証を使用するAPI(たとえば、APIとのAJAXインタラクションを含むWebサイト)のテストに適しています。

.credentials(**kwargs)

credentialsメソッドは、テストクライアントによる後続のすべてのリクエストに含めるヘッダーを設定するために使用できます。

from rest_framework.authtoken.models import Token
from rest_framework.test import APIClient

# Include an appropriate `Authorization:` header on all requests.
token = Token.objects.get(user__username='lauren')
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)

credentialsを2回目に呼び出すと、既存の資格情報が上書きされることに注意してください。引数なしでメソッドを呼び出すと、既存の資格情報をすべて設定解除できます。

# Stop including any credentials
client.credentials()

credentialsメソッドは、基本認証、OAuth1aおよびOAuth2認証、単純なトークン認証スキームなど、認証ヘッダーを必要とするAPIのテストに適しています。

.force_authenticate(user=None, token=None)

場合によっては、認証を完全にバイパスして、テストクライアントによるすべてのリクエストが自動的に認証済みとして扱われるように強制したい場合があります。

これは、APIをテストしているが、テストリクエストを作成するために有効な認証資格情報を構築する必要がない場合に便利なショートカットになります。

user = User.objects.get(username='lauren')
client = APIClient()
client.force_authenticate(user=user)

後続のリクエストの認証を解除するには、ユーザーおよび/またはトークンをNoneに設定してforce_authenticateを呼び出します。

client.force_authenticate(user=None)

CSRF検証

デフォルトでは、APIClientを使用する場合、CSRF検証は適用されません。CSRF検証を明示的に有効にする必要がある場合は、クライアントをインスタンス化するときにenforce_csrf_checksフラグを設定することで有効にできます。

client = APIClient(enforce_csrf_checks=True)

通常どおり、CSRF検証はセッション認証されたビューにのみ適用されます。これは、クライアントがlogin()を呼び出してログインした場合にのみCSRF検証が行われることを意味します。


RequestsClient

REST frameworkには、一般的なPythonライブラリであるrequestsを使用してアプリケーションと対話するためのクライアントも含まれています。これは、次の場合に役立つ可能性があります。

  • 主に別のPythonサービスからAPIとインターフェースすることが予想され、クライアントが見るのと同じレベルでサービスをテストしたい場合。
  • ステージング環境またはライブ環境に対して実行することもできるような方法でテストを作成したい場合。(下記の「ライブテスト」を参照。)

これは、リクエストセッションを直接使用している場合とまったく同じインターフェースを公開します。

from rest_framework.test import RequestsClient

client = RequestsClient()
response = client.get('http://testserver/users/')
assert response.status_code == 200

リクエストクライアントでは、完全修飾URLを渡す必要があることに注意してください。

RequestsClientとデータベースの連携

RequestsClientクラスは、サービスインターフェースのみと対話するテストを作成したい場合に役立ちます。これは、すべてのインタラクションがAPI経由で行われる必要があることを意味するため、標準のDjangoテストクライアントを使用するよりも少し厳密です。

RequestsClientを使用している場合は、テストのセットアップと結果のアサーションが、データベースモデルと直接対話するのではなく、通常のAPI呼び出しとして実行されるようにする必要があります。たとえば、Customer.objects.count() == 3を確認するのではなく、顧客エンドポイントをリストし、3つのレコードが含まれていることを確認します。

ヘッダーと認証

カスタムヘッダーと認証資格情報は、標準のrequests.Sessionインスタンスを使用する場合と同じ方法で提供できます。

from requests.auth import HTTPBasicAuth

client.auth = HTTPBasicAuth('user', 'pass')
client.headers.update({'x-test': 'true'})

CSRF

SessionAuthenticationを使用している場合は、POSTPUTPATCH、またはDELETEリクエストに対してCSRFトークンを含める必要があります。

これは、JavaScriptベースのクライアントが使用するのと同じフローに従って行うことができます。最初に、CSRFトークンを取得するためにGETリクエストを作成し、次のリクエストでそのトークンを提示します。

例...

client = RequestsClient()

# Obtain a CSRF token.
response = client.get('http://testserver/homepage/')
assert response.status_code == 200
csrftoken = response.cookies['csrftoken']

# Interact with the API.
response = client.post('http://testserver/organisations/', json={
    'name': 'MegaCorp',
    'status': 'active'
}, headers={'X-CSRFToken': csrftoken})
assert response.status_code == 200

ライブテスト

RequestsClientCoreAPIClientの両方を注意深く使用すると、開発環境で実行できるか、ステージングサーバーまたは実稼働環境に対して直接実行できるテストケースを作成できます。

このスタイルを使用して、いくつかのコア機能の基本的なテストを作成することは、ライブサービスを検証するための強力な方法です。これを行うには、顧客データに直接影響を与えないようにテストを実行するために、セットアップと破棄に注意を払う必要がある場合があります。


CoreAPIClient

CoreAPIClientを使用すると、Pythonのcoreapiクライアントライブラリを使用してAPIと対話できます。

# Fetch the API schema
client = CoreAPIClient()
schema = client.get('http://testserver/schema/')

# Create a new organisation
params = {'name': 'MegaCorp', 'status': 'active'}
client.action(schema, ['organisations', 'create'], params)

# Ensure that the organisation exists in the listing
data = client.action(schema, ['organisations', 'list'])
assert(len(data) == 1)
assert(data == [{'name': 'MegaCorp', 'status': 'active'}])

ヘッダーと認証

カスタムヘッダーと認証は、RequestsClientと同様の方法でCoreAPIClientで使用できます。

from requests.auth import HTTPBasicAuth

client = CoreAPIClient()
client.session.auth = HTTPBasicAuth('user', 'pass')
client.session.headers.update({'x-test': 'true'})

APIテストケース

REST frameworkには、既存のDjangoのテストケースクラスをミラーリングする次のテストケースクラスが含まれていますが、DjangoのデフォルトのClientの代わりにAPIClientを使用します。

  • APISimpleTestCase
  • APITransactionTestCase
  • APITestCase
  • APILiveServerTestCase

通常のDjangoテストケースクラスの場合と同様に、REST frameworkのテストケースクラスを使用できます。self.client属性はAPIClientインスタンスになります。

from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from myproject.apps.core.models import Account

class AccountTests(APITestCase):
    def test_create_account(self):
        """
        Ensure we can create a new account object.
        """
        url = reverse('account-list')
        data = {'name': 'DabApps'}
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(Account.objects.count(), 1)
        self.assertEqual(Account.objects.get().name, 'DabApps')

URLPatternsTestCase

REST frameworkは、クラスごとにurlpatternsを分離するためのテストケースクラスも提供します。これはDjangoのSimpleTestCaseから継承し、他のテストケースクラスと組み合わせて使用する必要がある可能性が高いことに注意してください。

from django.urls import include, path, reverse
from rest_framework.test import APITestCase, URLPatternsTestCase


class AccountTests(APITestCase, URLPatternsTestCase):
    urlpatterns = [
        path('api/', include('api.urls')),
    ]

    def test_create_account(self):
        """
        Ensure we can create a new account object.
        """
        url = reverse('account-list')
        response = self.client.get(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)

レスポンスのテスト

レスポンスデータのチェック

テストレスポンスの妥当性を確認する際、完全にレンダリングされたレスポンスを検査するよりも、レスポンスが作成された元となるデータを検査する方が便利な場合がよくあります。

例えば、response.content をパースした結果を検査するよりも、response.data を検査する方が簡単です。

response = self.client.get('/users/4/')
self.assertEqual(response.data, {'id': 4, 'username': 'lauren'})

response.content をパースした結果を検査するよりも

response = self.client.get('/users/4/')
self.assertEqual(json.loads(response.content), {'id': 4, 'username': 'lauren'})

レスポンスのレンダリング

APIRequestFactory を使ってビューを直接テストする場合、テンプレートレスポンスのレンダリングは Django の内部リクエスト-レスポンスサイクルによって実行されるため、返されるレスポンスはまだレンダリングされていません。response.content にアクセスするには、まずレスポンスをレンダリングする必要があります。

view = UserDetail.as_view()
request = factory.get('/users/4')
response = view(request, pk='4')
response.render()  # Cannot access `response.content` without this.
self.assertEqual(response.content, '{"username": "lauren", "id": 4}')

設定

デフォルトフォーマットの設定

テストリクエストの作成に使用するデフォルトのフォーマットは、TEST_REQUEST_DEFAULT_FORMAT 設定キーを使用して設定できます。例えば、標準のマルチパートフォームリクエストの代わりに、デフォルトで常に JSON をテストリクエストに使用するには、settings.py ファイルに次のように設定します。

REST_FRAMEWORK = {
    ...
    'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}

利用可能なフォーマットの設定

マルチパートリクエストや JSON リクエスト以外のものを使用してリクエストをテストする必要がある場合は、TEST_REQUEST_RENDERER_CLASSES 設定を設定することで対応できます。

例えば、テストリクエストで format='html' を使用するサポートを追加するには、settings.py ファイルに次のように記述します。

REST_FRAMEWORK = {
    ...
    'TEST_REQUEST_RENDERER_CLASSES': [
        'rest_framework.renderers.MultiPartRenderer',
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.TemplateHTMLRenderer'
    ]
}