Тепер, коли ми налаштували наше тестове середовище, навчилися тестувати серіалізатори та писати прості тести для API, час перейти до більшого — тестування представлень і ViewSet. Саме цей шар відповідає за логіку обробки наших запитів, і, як показує практика, якщо десь у коді щось "горить", це зазвичай відбувається тут. Тож беремося за гасіння потенційних пожеж!
Уявіть, що у вас є робот-кухар (представлення), який спочатку приймає на себе замовлення (HTTP-запит), потім передає його на кухню (сериалізатор або модель), а після повертає готову страву (відповідь). Що станеться, якщо робот забуде запитати про соус або почне приносити вам суп замість піци? Саме тестування представлень допомагає переконатися, що всі замовлення обробляються коректно і повертаються у потрібному вигляді.
Ось що ми будемо перевіряти:
- Коректні відповіді на запити. Наприклад,
GETповертає дані,POSTстворює запис, аDELETEвидаляє. - Логіка обробки даних. Чи всі параметри використовуються, як задумано?
- Дозволи та автентифікація. Чи доступний endpoint тільки авторизованим користувачам? А як щодо адміністраторів?
- Правильна робота 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="Книга 1", author="Автор 1")
Book.objects.create(title="Книга 2", 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'] == "Книга 1"
assert data[0]['author'] == "Автор 1"
Тестування методу POST
Тепер створимо нову книгу через POST запит і перевіримо, що вона додалася в базу даних.
@pytest.mark.django_db
def test_create_book(api_client):
data = {"title": "Нова Книга", "author": "Новий Автор"}
# Робимо POST-запит
response = api_client.post(reverse('book-list'), data)
# Перевіряємо статус відповіді
assert response.status_code == 201 # Статус успішного створення
# Перевіряємо, що книга створилася
book = Book.objects.get(title="Нова Книга")
assert book.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.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ