Теперь, когда мы настроили нашу тестовую среду, научились тестировать сериализаторы и писать простые тесты для API, пора перейти к большему — тестированию представлений и ViewSet. Именно этот слой отвечает за логику обработки наших запросов, и, как показывает практика, если где-то в коде что-то "горит", это обычно происходит здесь. Так что примемся за тушение потенциальных пожаров!
Представьте, что у вас есть робот-повар (представление), который сначала принимает на себя заказ (HTTP-запрос), затем передает его на кухню (сериализатор или модель), а после возвращает готовое блюдо (ответ). Что случится, если робот забудет спросить про соус или начнет приносить вам суп вместо пиццы? Именно тестирование представлений помогает убедиться, что все заказы обрабатываются корректно и возвращаются в нужном виде.
Вот что мы будем проверять:
- Корректные ответы на запросы. Например,
GETвозвращает данные,POSTсоздает запись, аDELETEудаляет. - Логика обработки данных. Все ли параметры используются, как задумано?
- Разрешения и аутентификация. Доступен ли эндпоинт только авторизованным пользователям? А что насчет админов?
- Правильная работа ViewSet. Проверка стандартного поведения и кастомных действий.
Как тестировать представления?
Большинство представлений возвращает HTTP-ответы, поэтому нам нужно имитировать запросы (GET, POST и т.д.) и проверять, что они возвращают ожидаемые значения. Мы будем использовать DRF тест-клиент, который делает процесс тестирования приятным и интуитивным.
Пример
Предположим, что в нашем проекте есть модель Book с полями title и author. Мы уже реализовали ViewSet для обработки CRUD-операций с Book. Теперь будем тестировать его с разных углов.
# models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=255)
author = models.CharField(max_length=255)
# views.py
from rest_framework.viewsets import ModelViewSet
from .models import Book
from .serializers import BookSerializer
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
Написание тестов для ViewSet
Подготовка данных: фикстуры и setUp
Прежде чем тестировать, нам нужны данные. Мы создадим фикстуры — своего рода "тестовые пирожки", чтобы ViewSet было что обрабатывать.
import pytest
from rest_framework.test import APIClient
from django.urls import reverse
from .models import Book
@pytest.fixture
def api_client():
return APIClient()
@pytest.fixture
def create_books():
Book.objects.create(title="Book 1", author="Author 1")
Book.objects.create(title="Book 2", author="Author 2")
Тестирование метода GET
Проверим, что наш GET запрос возвращает список книг, и каждая книга содержит нужные данные.
api_client и
create_books.
@pytest.mark.django_db
def test_get_books(api_client, create_books):
# Делаем GET-запрос на эндпоинт `/books/`
response = api_client.get(reverse('book-list')) # 'book-list' — имя маршрута.
# Проверяем статус ответа
assert response.status_code == 200
# Проверяем, что возвращается 2 книги
data = response.json()
assert len(data) == 2
assert data[0]['title'] == "Book 1"
assert data[0]['author'] == "Author 1"
Тестирование метода POST
Теперь создадим новую книгу через POST запрос и проверим, что она добавилась в базу данных.
@pytest.mark.django_db
def test_create_book(api_client):
data = {"title": "New Book", "author": "New Author"}
# Делаем POST-запрос
response = api_client.post(reverse('book-list'), data)
# Проверяем статус ответа
assert response.status_code == 201 # Статус успешного создания
# Проверяем, что книга создалась
book = Book.objects.get(title="New Book")
assert book.author == "New Author"
Тестирование метода DELETE
Проверим, что книга удаляется корректно через DELETE.
@pytest.mark.django_db
def test_delete_book(api_client, create_books):
book = Book.objects.first()
url = reverse('book-detail', args=[book.id]) # 'book-detail' — путь для конкретного объекта.
# Делаем DELETE-запрос
response = api_client.delete(url)
# Проверяем статус ответа
assert response.status_code == 204 # Успешное удаление
# Проверяем, что книга удалена
assert not Book.objects.filter(id=book.id).exists()
Проверка разрешений и аутентификации
Теперь представьте, что книги можно удалять только админам. Мы настроим разрешения так, чтобы у обычных пользователей был закрыт доступ.
from rest_framework.permissions import IsAdminUser
# views.py
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = [IsAdminUser] # Удалять книги могут только админы
Тест для проверки разрешений
В этом тесте мы создадим администратора и обычного пользователя, чтобы проверить доступ.
from django.contrib.auth.models import User
@pytest.mark.django_db
def test_delete_book_permission(api_client, create_books):
book = Book.objects.first()
url = reverse('book-detail', args=[book.id])
# Попробуем удалить книгу без авторизации
response = api_client.delete(url)
assert response.status_code == 403 # Доступ запрещен
# Авторизуем администратора
admin_user = User.objects.create_superuser(username="admin", password="admin_pass")
api_client.force_authenticate(user=admin_user)
# Попробуем удалить книгу снова
response = api_client.delete(url)
assert response.status_code == 204 # Успешное удаление
Тестирование кастомных методов ViewSet
Допустим, у нас есть кастомный метод для поиска книг по автору. Мы добавим этот метод в наш ViewSet и проверим его в тестах.
# views.py
from rest_framework.decorators import action
from rest_framework.response import Response
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
@action(detail=False, methods=['get'])
def search_by_author(self, request):
author = request.query_params.get('author')
books = self.queryset.filter(author=author)
serializer = self.get_serializer(books, many=True)
return Response(serializer.data)
Тест кастомного метода
@pytest.mark.django_db
def test_custom_action(api_client, create_books):
# Делаем GET-запрос с параметром автора
response = api_client.get(reverse('book-search-by-author'), {'author': 'Author 1'})
# Проверяем статус ответа и данные
assert response.status_code == 200
data = response.json()
assert len(data) == 1
assert data[0]['title'] == "Book 1"
Подводим итоги
Теперь мы можем тестировать:
- Стандартное поведение ViewSet.
- Работу с разрешениями и аутентификацией.
- Кастомные методы ViewSet.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ