JavaRush /Курси /Модуль 3: Django /Тестування представлень і ViewSet у Django

Тестування представлень і ViewSet у Django

Модуль 3: Django
Рівень 22 , Лекція 6
Відкрита

Тепер, коли ми налаштували наше тестове середовище, навчилися тестувати серіалізатори та писати прості тести для 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.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ