テスト
テストのないコードは、設計どおりに壊れています。
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
引数の使用
post
、put
、patch
など、リクエストボディを作成するメソッドには、マルチパートフォームデータ以外のコンテンツタイプを使用してリクエストを簡単に生成できるようにする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
を使用している場合は、POST
、PUT
、PATCH
、または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
ライブテスト
RequestsClient
とCoreAPIClient
の両方を注意深く使用すると、開発環境で実行できるか、ステージングサーバーまたは実稼働環境に対して直接実行できるテストケースを作成できます。
このスタイルを使用して、いくつかのコア機能の基本的なテストを作成することは、ライブサービスを検証するための強力な方法です。これを行うには、顧客データに直接影響を与えないようにテストを実行するために、セットアップと破棄に注意を払う必要がある場合があります。
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'
]
}