“没有测试的代码,从设计上就是有缺陷的。”——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
# 使用标准的 RequestFactory API 创建一个表单 POST 请求
factory = APIRequestFactory()
request = factory.post('/notes/', {'title': 'new idea'})
# 使用标准的 RequestFactory API 编码 JSON 数据
request = factory.post('/notes/', {'title': 'new idea'}, content_type='application/json')
使用 format
参数
像 post
、put
和 patch
这样会创建请求体的方法,包含一个 format
参数,这使得使用多种请求格式生成请求变得容易。当使用这个参数时,工厂会选择合适的渲染器及其配置的 content_type
。例如:
# 创建一个 JSON POST 请求
factory = APIRequestFactory()
request = factory.post('/notes/', {'title': 'new idea'}, format='json')
默认情况下,可用的格式是 'multipart'
和 'json'
。为了与 Django 现有的 RequestFactory
兼容,默认格式是 'multipart'
。
为了支持更广泛的请求格式,或更改默认格式,请参阅配置部分。
显式编码请求体
如果你需要显式编码请求体,可以通过设置 content_type
标志来实现。例如:
request = factory.post('/notes/', yaml.dump({'title': 'new idea'}), content_type='application/yaml')
使用表单数据进行 PUT 和 PATCH 操作
APIRequestFactory
和 Django 的 RequestFactory
之间一个值得注意的区别是,对于不仅仅是 .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()
# 发出一个经过认证的请求到视图...
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
对象,后者只有在调用视图时才会生成。这意味着直接在请求对象上设置属性可能不会总是产生你期望的效果。例如,直接设置 .token
将不会起作用,而直接设置 .user
只有在使用会话认证时才会起作用。
# 只有当使用 `SessionAuthentication` 时,请求才会被认证。
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
的任何视图认证请求。
# 在登录会话的上下文中发出所有请求。
client = APIClient()
client.login(username='lauren', password='secret')
要登出,像往常一样调用 logout
方法。
# 登出
client.logout()
当使用会话认证时,例如在包含 AJAX 交互的网站中,login
方法是合适的。
.credentials(**kwargs*)
credentials
方法可用于设置将在测试客户端后续发出的所有请求中包含的头部。
from rest_framework.authtoken.models import Token
from rest_framework.test import APIClient
# 在所有请求中包含适当的 `Authorization:` 头部。
token = Token.objects.get(user__username='lauren')
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)
注意,第二次调用 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()
登录的会话认证视图。这意味着只有当客户端通过 login()
登录时,CSRF 验证才会发生。
RequestsClient
REST framework 还包含一个客户端,用于使用流行的 Python 库 requests
与你的应用程序进行交互。这在以下情况下可能很有用:
- 你期望主要从另一个 Python 服务与 API 进行交互,并希望以客户端将看到的级别来测试服务。
- 你希望编写可以针对开发环境、预生产环境或生产环境运行的测试。(参阅 “实时测试” 。)
这暴露了与直接使用 requests
会话完全相同的接口。
from rest_framework.test import RequestsClient
client = RequestsClient()
response = client.get('http://testserver/users/')
assert response.status_code == 200
注意,requests
客户端要求你传递完整的 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()
# 获取 CSRF 令牌。
response = client.get('http://testserver/homepage/')
assert response.status_code == 200
csrftoken = response.cookies['csrftoken']
# 与 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 进行交互。
# 获取 API 架构
client = CoreAPIClient()
schema = client.get('http://testserver/schema/')
# 创建一个新组织
params = {'name': 'MegaCorp', 'status': 'active'}
client.action(schema, ['organisations', 'create'], params)
# 确保组织存在于列表中
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 现有的测试用例类相对应,但使用 APIClient
而不是 Django 默认的 Client
:
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):
"""
确保我们能够创建一个新的账户对象。
"""
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):
"""
确保我们能够创建一个新的账户对象。
"""
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() # 在访问 `response.content` 之前必须这样做。
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'
]
}