APIClient, тесты для views, моки, best practices тестирования
DRF предоставляет мощные инструменты для тестирования API: APIClient, force_authenticate, тестовые запросы и моки.
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)Принудительная аутентификация без токена:
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)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)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)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()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)
# Первая статья — самая новаяresponses или unittest.mockВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.