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

Тестирование представлений и ViewSet в Django

Модуль 3: Django
22 уровень , 6 лекция
Открыта

Теперь, когда мы настроили нашу тестовую среду, научились тестировать сериализаторы и писать простые тесты для 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.
1
Задача
Модуль 3: Django, 22 уровень, 6 лекция
Недоступна
Тестирование получения данных с помощью ViewSet
Тестирование получения данных с помощью ViewSet
1
Задача
Модуль 3: Django, 22 уровень, 6 лекция
Недоступна
Тестирование всех основных методов ViewSet
Тестирование всех основных методов ViewSet
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ