Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Тестирование DRF
testing

Тестирование DRF

APIClient, тесты для views, моки, best practices тестирования

Тестирование в Django REST Framework

DRF предоставляет мощные инструменты для тестирования API: APIClient, force_authenticate, тестовые запросы и моки.

#APIClient

APIClient — расширенная версия Django Client для тестирования DRF API:

from rest_framework.test import APIClient from rest_framework.test import APITestCase class ArticleTests(APITestCase): def setUp(self): self.client = APIClient() self.user = User.objects.create_user( username='testuser', password='testpass123' ) self.article = Article.objects.create( title='Test Article', content='Test content', author=self.user ) def test_list_articles(self): response = self.client.get('/api/articles/') self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 1) def test_create_article(self): data = { 'title': 'New Article', 'content': 'New content', } response = self.client.post('/api/articles/', data) self.assertEqual(response.status_code, 201) self.assertEqual(Article.objects.count(), 2)

#Аутентификация в тестах

#force_authenticate

Принудительная аутентификация без токена:

from rest_framework.test import APITestCase, force_authenticate from rest_framework.test import APIClient class AuthenticatedTests(APITestCase): def setUp(self): self.client = APIClient() self.user = User.objects.create_user( username='testuser', password='testpass123' ) def test_create_article_authenticated(self): # Принудительная аутентификация self.client.force_authenticate(user=self.user) data = {'title': 'Article', 'content': 'Content'} response = self.client.post('/api/articles/', data) self.assertEqual(response.status_code, 201) self.assertEqual(response.data['author'], self.user.id) def test_create_article_anonymous(self): # Без аутентификации data = {'title': 'Article', 'content': 'Content'} response = self.client.post('/api/articles/', data) self.assertEqual(response.status_code, 401)

#Аутентификация с токеном

from rest_framework.authtoken.models import Token class TokenAuthTests(APITestCase): def setUp(self): self.client = APIClient() self.user = User.objects.create_user( username='testuser', password='testpass123' ) self.token = Token.objects.create(user=self.user) def test_create_with_token(self): self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token.key) data = {'title': 'Article', 'content': 'Content'} response = self.client.post('/api/articles/', data) self.assertEqual(response.status_code, 201) def test_remove_credentials(self): self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token.key) # Logout — удалить credentials self.client.credentials() data = {'title': 'Article', 'content': 'Content'} response = self.client.post('/api/articles/', data) self.assertEqual(response.status_code, 401)

#JWT аутентификация в тестах

from rest_framework_simplejwt.tokens import RefreshToken class JWTAuthTests(APITestCase): def setUp(self): self.client = APIClient() self.user = User.objects.create_user( username='testuser', password='testpass123' ) refresh = RefreshToken.for_user(self.user) self.access_token = str(refresh.access_token) def test_create_with_jwt(self): self.client.credentials( HTTP_AUTHORIZATION='Bearer ' + self.access_token ) data = {'title': 'Article', 'content': 'Content'} response = self.client.post('/api/articles/', data) self.assertEqual(response.status_code, 201)

#Тестирование статус-кодов

from rest_framework import status class StatusTests(APITestCase): def setUp(self): self.client = APIClient() self.user = User.objects.create_user( username='testuser', password='testpass123' ) def test_200_ok(self): response = self.client.get('/api/articles/') self.assertEqual(response.status_code, status.HTTP_200_OK) def test_201_created(self): self.client.force_authenticate(user=self.user) data = {'title': 'Article', 'content': 'Content'} response = self.client.post('/api/articles/', data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_400_bad_request(self): self.client.force_authenticate(user=self.user) data = {'title': ''} # Пустой заголовок response = self.client.post('/api/articles/', data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_401_unauthorized(self): data = {'title': 'Article', 'content': 'Content'} response = self.client.post('/api/articles/', data) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test_403_forbidden(self): other_user = User.objects.create_user( username='otheruser', password='testpass123' ) self.client.force_authenticate(user=other_user) # Попытка удалить чужую статью response = self.client.delete(f'/api/articles/{self.article.id}/') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_404_not_found(self): response = self.client.get('/api/articles/999/') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

#Тестирование валидации

class ValidationTests(APITestCase): def setUp(self): self.client = APIClient() self.user = User.objects.create_user( username='testuser', password='testpass123' ) def test_validation_errors(self): self.client.force_authenticate(user=self.user) # Пустые обязательные поля data = {'title': '', 'content': ''} response = self.client.post('/api/articles/', data) self.assertEqual(response.status_code, 400) self.assertIn('title', response.data) self.assertIn('content', response.data) def test_field_max_length(self): self.client.force_authenticate(user=self.user) data = { 'title': 'A' * 300, # Максимум 200 символов 'content': 'Content' } response = self.client.post('/api/articles/', data) self.assertEqual(response.status_code, 400) self.assertIn('title', response.data)

#Тестирование ViewSet

from rest_framework import viewsets from rest_framework.test import APITestCase class ViewSetTests(APITestCase): def setUp(self): self.client = APIClient() self.user = User.objects.create_user( username='testuser', password='testpass123' ) self.article = Article.objects.create( title='Test Article', content='Test content', author=self.user ) def test_list(self): response = self.client.get('/api/articles/') self.assertEqual(response.status_code, 200) def test_retrieve(self): response = self.client.get(f'/api/articles/{self.article.id}/') self.assertEqual(response.status_code, 200) self.assertEqual(response.data['title'], 'Test Article') def test_create(self): self.client.force_authenticate(user=self.user) data = {'title': 'New Article', 'content': 'New content'} response = self.client.post('/api/articles/', data) self.assertEqual(response.status_code, 201) def test_update(self): self.client.force_authenticate(user=self.user) data = {'title': 'Updated Title', 'content': 'Updated content'} response = self.client.put(f'/api/articles/{self.article.id}/', data) self.assertEqual(response.status_code, 200) self.article.refresh_from_db() self.assertEqual(self.article.title, 'Updated Title') def test_partial_update(self): self.client.force_authenticate(user=self.user) data = {'title': 'Partial Update'} response = self.client.patch(f'/api/articles/{self.article.id}/', data) self.assertEqual(response.status_code, 200) self.article.refresh_from_db() self.assertEqual(self.article.title, 'Partial Update') def test_destroy(self): self.client.force_authenticate(user=self.user) response = self.client.delete(f'/api/articles/{self.article.id}/') self.assertEqual(response.status_code, 204) self.assertEqual(Article.objects.count(), 0)

#Моки и patch

#Использование unittest.mock

from unittest.mock import patch, MagicMock class MockTests(APITestCase): @patch('myapp.views.send_email_notification') def test_create_article_sends_email(self, mock_send_email): self.client.force_authenticate(user=self.user) data = {'title': 'Article', 'content': 'Content'} response = self.client.post('/api/articles/', data) self.assertEqual(response.status_code, 201) mock_send_email.assert_called_once() @patch('myapp.models.Article.save') def test_article_save_called(self, mock_save): mock_save.return_value = None article = Article(title='Test', content='Content') article.save() mock_save.assert_called_once()

#Mock внешних API

import responses class ExternalAPITests(APITestCase): @responses.activate def test_create_with_external_api(self): # Мок внешнего API responses.add( responses.POST, 'https://external-api.com/notify', json={'status': 'success'}, status=200 ) self.client.force_authenticate(user=self.user) data = {'title': 'Article', 'content': 'Content'} response = self.client.post('/api/articles/', data) self.assertEqual(response.status_code, 201) self.assertEqual(len(responses.calls), 1)

#Тестирование пагинации

class PaginationTests(APITestCase): def setUp(self): self.client = APIClient() # Создаём 50 статей for i in range(50): Article.objects.create( title=f'Article {i}', content=f'Content {i}', author=self.user ) def test_pagination(self): response = self.client.get('/api/articles/') self.assertEqual(response.status_code, 200) self.assertIn('count', response.data) self.assertIn('next', response.data) self.assertIn('previous', response.data) self.assertIn('results', response.data) self.assertEqual(response.data['count'], 50) self.assertEqual(len(response.data['results']), 20) # PAGE_SIZE = 20 def test_pagination_page_2(self): response = self.client.get('/api/articles/?page=2') self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.data['next']) self.assertIsNotNone(response.data['previous'])

#Тестирование фильтрации и поиска

class FilterTests(APITestCase): def setUp(self): self.client = APIClient() Article.objects.create(title='Python Guide', content='...', author=self.user) Article.objects.create(title='Django Guide', content='...', author=self.user) Article.objects.create(title='DRF Guide', content='...', author=self.user) def test_search(self): response = self.client.get('/api/articles/?search=Guide') self.assertEqual(response.status_code, 200) self.assertEqual(response.data['count'], 3) def test_filter(self): response = self.client.get('/api/articles/?title__icontains=Python') self.assertEqual(response.status_code, 200) self.assertEqual(response.data['count'], 1) def test_ordering(self): response = self.client.get('/api/articles/?ordering=-created_at') self.assertEqual(response.status_code, 200) # Первая статья — самая новая

#Best Practices

  1. Используйте APITestCase — предоставляет self.client как APIClient
  2. force_authenticate для тестов с авторизацией — проще чем токены
  3. setUp для общих данных — создавайте пользователя и данные один раз
  4. Тестируйте все статус-коды — 200, 201, 400, 401, 403, 404
  5. Тестируйте валидацию — проверяйте ошибки валидации
  6. Мокайте внешние API — используйте responses или unittest.mock
  7. Изолируйте тесты — каждый тест должен быть независимым

Далее: Документирование API