测试
没有测试的代码在设计上就是有缺陷的。
REST 框架包含一些帮助类,它们扩展了 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
参数,使用它可以轻松生成使用除 multipart 表单数据之外的其他内容类型的请求。例如
# 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 框架的 APIRequestFactory
之间的一个区别在于,multipart 表单数据将被编码为除 .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 框架的 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 框架视图时不会应用 CSRF 验证。如果您需要明确启用 CSRF 验证,可以在实例化工厂时设置 enforce_csrf_checks
标志来实现。
factory = APIRequestFactory(enforce_csrf_checks=True)
注意:值得注意的是,Django 的标准 RequestFactory
不需要包含此选项,因为在使用常规 Django 时,CSRF 验证在中间件中进行,而直接测试视图时不会运行中间件。使用 REST 框架时,CSRF 验证在视图内进行,因此请求工厂需要禁用视图级别的 CSRF 检查。
APIClient
发出请求
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 交互的网站。
.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
会覆盖任何现有凭据。您可以通过不带任何参数调用该方法来取消设置任何现有凭据。
# Stop including any credentials
client.credentials()
credentials
方法适用于测试需要身份验证标头的 API,例如基本身份验证、OAuth1a 和 OAuth2 身份验证以及简单的令牌身份验证方案。
.force_authenticate(user=None, token=None)
有时您可能希望完全绕过身份验证,并强制测试客户端的所有请求自动视为已通过身份验证。
如果您正在测试 API,但不想构造有效的身份验证凭据来进行测试请求,这可能是一个有用的快捷方式。
user = User.objects.get(username='lauren')
client = APIClient()
client.force_authenticate(user=user)
要取消对后续请求进行身份验证,请调用 force_authenticate
,将用户和/或令牌设置为 None
。
client.force_authenticate(user=None)
CSRF 验证
默认情况下,在使用 APIClient
时不应用 CSRF 验证。如果您需要显式启用 CSRF 验证,可以在实例化客户端时设置 enforce_csrf_checks
标志来执行此操作。
client = APIClient(enforce_csrf_checks=True)
与往常一样,CSRF 验证仅适用于任何会话身份验证视图。这意味着仅当客户端通过调用 login()
登录后,才会进行 CSRF 验证。
RequestsClient
REST 框架还包括一个客户端,用于使用流行的 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
类非常有用。这比使用标准的 Django 测试客户端要严格一些,因为它意味着所有交互都应通过 API 进行。
如果您使用的是 RequestsClient
,您需要确保测试设置和结果断言作为常规 API 调用执行,而不是直接与数据库模型交互。例如,不要检查 Customer.objects.count() == 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 客户端将使用的方法来做到这一点。首先,执行 GET
请求以获取 CSRF 令牌,然后在以下请求中显示该令牌。
例如...
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'}])
标头和认证
自定义标头和身份验证可以与 CoreAPIClient
一起使用,方式与 RequestsClient
类似。
from requests.auth import HTTPBasicAuth
client = CoreAPIClient()
client.session.auth = HTTPBasicAuth('user', 'pass')
client.session.headers.update({'x-test': 'true'})
API 测试用例
REST 框架包括以下测试用例类,它们反映了现有的 Django 的测试用例类,但使用 APIClient
而不是 Django 的默认 Client
。
APISimpleTestCase
APITransactionTestCase
APITestCase
APILiveServerTestCase
示例
您可以像使用常规 Django 测试用例类一样使用 REST 框架的任何测试用例类。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 框架还提供了一个测试用例类,用于按每个类隔离 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.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'
]
}