Тестирование¶
Код без тестов сломан, как и было задумано.
‒ Jacob Kaplan-Moss
Фреймворк REST включает несколько вспомогательных классов, которые расширяют существующую тестовую структуру Django и улучшают поддержку выполнения API-запросов.
APIRequestFactory¶
Расширяет Django’s existing ``RequestFactory:doc:` class <https://docs.djangoproject.com/en/stable/topics/testing/advanced/#django.test.client.RequestFactory>`.
Создание тестовых запросов¶
Класс APIRequestFactory
поддерживает почти идентичный API стандартному классу Django RequestFactory
. Это означает, что все стандартные методы .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'
.
Чтобы поддержать более широкий набор форматов запросов или изменить формат по умолчанию, see the configuration section.
Явное кодирование тела запроса¶
Если вам необходимо явно закодировать тело запроса, вы можете сделать это, установив флаг content_type
. Например:
request = factory.post('/notes/', json.dumps({'title': 'new idea'}), content_type='application/json')
PUT и PATCH с данными формы¶
Стоит отметить одно отличие между RequestFactory
Django и APIRequestFactory
фреймворка REST в том, что данные многочастной формы будут закодированы для методов, отличных от .post()
.
Например, используя APIRequestFactory
, вы можете сделать запрос PUT в виде формы следующим образом:
factory = APIRequestFactory()
request = factory.put('/notes/547/', {'title': 'remember to email dave'})
Используя RequestFactory
в Django, вам придется явно кодировать данные самостоятельно:
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():doc:` <https://docs.djangoproject.com/en/1.11/ref/models/instances/#django.db.models.Model.refresh_from_db>` между тестами.
Примечание: При использовании APIRequestFactory
возвращаемый объект - это стандартный HttpRequest
Django, а не объект Request
фреймворка REST, который генерируется только после вызова представления.
Это означает, что установка атрибутов непосредственно на объекте запроса не всегда может иметь ожидаемый эффект. Например, установка .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
, не будут иметь CSRF-валидации при передаче в представление REST-фреймворка. Если вам необходимо явно включить проверку CSRF, вы можете сделать это, установив флаг enforce_csrf_checks
при инстанцировании фабрики.
factory = APIRequestFactory(enforce_csrf_checks=True)
Примечание : Стоит отметить, что в стандартном Django RequestFactory
нет необходимости включать эту опцию, потому что при использовании обычного Django проверка CSRF происходит в промежуточном ПО, которое не запускается при тестировании представлений напрямую. При использовании фреймворка REST проверка CSRF происходит внутри представления, поэтому в фабрике запросов необходимо отключить проверку CSRF на уровне представления.
APIClient¶
Расширяет Django’s existing ``Client:doc:` class <https://docs.djangoproject.com/en/stable/topics/testing/tools/#the-test-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')
Чтобы поддержать более широкий набор форматов запросов или изменить формат по умолчанию, see the configuration section.
Аутентификация¶
.login(**kwargs)¶
Метод login
функционирует точно так же, как и обычный класс Client
в Django. Это позволяет вам аутентифицировать запросы к любым представлениям, которые включают 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, использующих сеансовую аутентификацию, например, веб-сайтов, включающих AJAX-взаимодействие с API.
.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¶
По умолчанию проверка CSRF не применяется при использовании APIClient
. Если вам необходимо явно включить проверку CSRF, вы можете сделать это, установив флаг enforce_csrf_checks
при инстанцировании клиента.
client = APIClient(enforce_csrf_checks=True)
Как обычно, проверка CSRF будет применяться только к любым представлениям с сеансовой аутентификацией. Это означает, что проверка CSRF будет происходить только в том случае, если клиент вошел в систему, вызвав login()
.
RequestsClient¶
REST framework также включает клиент для взаимодействия с вашим приложением с помощью популярной библиотеки Python, requests
. Это может быть полезно, если:
Вы ожидаете, что взаимодействие с API будет осуществляться в основном из другого сервиса Python, и хотите протестировать сервис на том же уровне, который будет видеть клиент.
Вы хотите написать тесты таким образом, чтобы их можно было запускать в рабочей среде. (См. раздел «Живые тесты» ниже).
Это предоставляет точно такой же интерфейс, как если бы вы использовали сессию запросов напрямую.
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
, вы бы перечислили конечную точку customers и убедились, что она содержит три записи.
Заголовки и аутентификация¶
Пользовательские заголовки и учетные данные аутентификации могут быть предоставлены таким же образом, как и when using a standard ``requests.Session:doc:` instance <https://requests.readthedocs.io/en/master/user/advanced/#session-objects>`.
from requests.auth import HTTPBasicAuth
client.auth = HTTPBasicAuth('user', 'pass')
client.headers.update({'x-test': 'true'})
CSRF¶
Если вы используете SessionAuthentication
, то вам необходимо включить CSRF токен для любого POST
, PUT
, PATCH
или DELETE
запроса.
Это можно сделать, следуя той же схеме, которую использует клиент на базе JavaScript. Сначала сделайте запрос GET
, чтобы получить маркер CRSF, затем представьте этот маркер в следующем запросе.
Например…
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 позволяет вам взаимодействовать с API с помощью клиентской библиотеки Python coreapi
.
# 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
вместо стандартного Client
в Django.
APISimpleTestCase
APITransactionTestCase
APITestCase
APILiveServerTestCase
Пример¶
Вы можете использовать любой из классов тестовых примеров фреймворка REST, как и для обычных классов тестовых примеров Django. Атрибут 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’s 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'
]
}