☰ Categories

Django Unit Test Generator for Viewsets

a Django Unit Test Generator. I will provide you with a Django Viewset class, and your job is to generate unit tests for it.

CategoryDevelopment › Coding
TagsDraftingDeveloperCode
Prompt
I want you to act as a Django Unit Test Generator. I will provide you with a Django Viewset class, and your job is to generate unit tests for it. Ensure the following:

1. Create test cases for all CRUD (Create, Read, Update, Delete) operations.
2. Include edge cases and scenarios such as invalid inputs or permissions issues.
3. Use Django's TestCase class and the APIClient for making requests.
4. Make use of setup methods to initialize any required data.

Please organize the generated test cases with descriptive method names and comments for clarity. Ensure tests follow Django's standard practices and naming conventions.

What this prompt does

Use this to draft tests for a Django Viewset quickly. It asks for descriptive method names and comments, though project-specific setup still needs checking.

Real outputs by model

Read full answers

We gave three models the same input and copied their answers unedited. Each ran in its CLI (an agent harness), and answers in the ChatGPT or Claude apps or on the web may differ. Outputs are in Korean.

Input used
[Pasted material]
from rest_framework import viewsets, permissions
from .models import Product
from .serializers import ProductSerializer

class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def get_queryset(self):
        qs = super().get_queryset()
        category = self.request.query_params.get('category')
        if category:
            qs = qs.filter(category__slug=category)
        return qs

    def perform_create(self, serializer):
        serializer.save(seller=self.request.user)
gpt-5.6-luna (codex CLI · 에이전트 하네스) · Run on 2026-09-24

아래 예시는 Product 모델에 name, description, price, category, seller 필드가 있고, Category 모델이 slug 필드를 가진다고 가정합니다. 프로젝트의 실제 필드명에 맞게 수정하세요.

from decimal import Decimal

from django.contrib.auth import get_user_model
from django.urls import reverse
from django.test import TestCase

from rest_framework import status
from rest_framework.test import APIClient

from .models import Category, Product


User = get_user_model()


class ProductViewSetTests(TestCase):
    def setUp(self):
        self.client = APIClient()

        self.user = User.objects.create_user(
            username="seller",
            password="test-password",
        )
        self.other_user = User.objects.create_user(
            username="other-seller",
            password="test-password",
        )

        self.category = Category.objects.create(
            name="Electronics",
            slug="electronics",
        )
        self.other_category = Category.objects.create(
            name="Books",
            slug="books",
        )

        self.product = Product.objects.create(
            name="Laptop",
            description="Developer laptop",
            price=Decimal("1200.00"),
            category=self.category,
            seller=self.user,
        )
        self.other_product = Product.objects.create(
            name="Book",
            description="Django book",
            price=Decimal("50.00"),
            category=self.other_category,
            seller=self.other_user,
        )

        # DRF Router에서 basename이 product라고 가정
        self.list_url = reverse("product-list")
        self.detail_url = reverse(
            "product-detail",
            kwargs={"pk": self.product.pk},
        )

    def authenticate(self, user=None):
        """테스트용 인증 헬퍼 메서드"""
        self.client.force_authenticate(user=user or self.user)

    def product_payload(self, **overrides):
        """상품 생성 및 수정 요청에 사용할 기본 payload"""
        payload = {
            "name": "New Product",
            "description": "New product description",
            "price": "99.99",
            "category": self.category.pk,
        }
        payload.update(overrides)
        return payload

    def test_anonymous_user_can_read_product_list(self):
        """비인증 사용자는 상품 목록을 조회할 수 있어야 한다."""
        response = self.client.get(self.list_url)

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 2)

    def test_anonymous_user_can_read_product_detail(self):
        """비인증 사용자는 상품 상세 정보를 조회할 수 있어야 한다."""
        response = self.client.get(self.detail_url)

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data["name"], self.product.name)

    def test_authenticated_user_can_create_product(self):
        """인증 사용자는 상품을 생성할 수 있어야 한다."""
        self.authenticate()

        response = self.client.post(
            self.list_url,
            self.product_payload(),
            format="json",
        )

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(Product.objects.count(), 3)

        created_product = Product.objects.get(pk=response.data["id"])
        self.assertEqual(created_product.seller, self.user)
        self.assertEqual(created_product.name, "New Product")

    def test_anonymous_user_cannot_create_product(self):
        """비인증 사용자는 상품을 생성할 수 없어야 한다."""
        response = self.client.post(
            self.list_url,
            self.product_payload(),
            format="json",
        )

        self.assertIn(
            response.status_code,
            [status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN],
        )
        self.assertEqual(Product.objects.count(), 2)

    def test_create_product_with_invalid_price_fails(self):
        """잘못된 가격을 입력하면 상품 생성이 실패해야 한다."""
        self.authenticate()

        response = self.client.post(
            self.list_url,
            self.product_payload(price="not-a-number"),
            format="json",
        )

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(Product.objects.count(), 2)

    def test_create_product_with_missing_required_field_fails(self):
        """필수 필드가 누락되면 상품 생성이 실패해야 한다."""
        self.authenticate()

        payload = self.product_payload()
        payload.pop("name")

        response = self.client.post(
            self.list_url,
            payload,
            format="json",
        )

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("name", response.data)

    def test_filter_products_by_category_slug(self):
        """category 쿼리 파라미터로 상품을 필터링할 수 있어야 한다."""
        response = self.client.get(
            self.list_url,
            {"category": self.category.slug},
        )

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)
        self.assertEqual(response.data[0]["name"], self.product.name)

    def test_filter_with_unknown_category_returns_empty_list(self):
        """존재하지 않는 카테고리로 필터링하면 빈 목록을 반환해야 한다."""
        response = self.client.get(
            self.list_url,
            {"category": "unknown-category"},
        )

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data, [])

    def test_authenticated_user_can_update_product(self):
        """인증 사용자는 상품을 수정할 수 있어야 한다."""
        self.authenticate()

        payload = self.product_payload(
            name="Updated Laptop",
            price="1500.00",
        )

        response = self.client.put(
            self.detail_url,
            payload,
            format="json",
        )

        self.assertEqual(response.status_code, status.HTTP_200_OK)

        self.product.refresh_from_db()
        self.assertEqual(self.product.name, "Updated Laptop")
        self.assertEqual(self.product.price, Decimal("1500.00"))

    def test_authenticated_user_can_partially_update_product(self):
        """인증 사용자는 상품 일부 필드만 수정할 수 있어야 한다."""
        self.authenticate()

        response = self.client.patch(
            self.detail_url,
            {"name": "Partially Updated Laptop"},
            format="json",
        )

        self.assertEqual(response.status_code, status.HTTP_200_OK)

        self.product.refresh_from_db()
        self.assertEqual(self.product.name, "Partially Updated Laptop")
        self.assertEqual(
            self.product.description,
            "Developer laptop",
        )

    def test_anonymous_user_cannot_update_product(self):
        """비인증 사용자는 상품을 수정할 수 없어야 한다."""
        response = self.client.patch(
            self.detail_url,
            {"name": "Unauthorized Update"},
            format="json",
        )

        self.assertIn(
            response.status_code,
            [status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN],
        )

        self.product.refresh_from_db()
        self.assertEqual(self.product.name, "Laptop")

    def test_update_product_with_invalid_data_fails(self):
        """잘못된 데이터로 상품을 수정할 수 없어야 한다."""
        self.authenticate()

        response = self.client.patch(
            self.detail_url,
            {"price": "invalid-price"},
            format="json",
        )

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

    def test_authenticated_user_can_delete_product(self):
        """인증 사용자는 상품을 삭제할 수 있어야 한다."""
        self.authenticate()

        response = self.client.delete(self.detail_url)

        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
        self.assertFalse(
            Product.objects.filter(pk=self.product.pk).exists()
        )

    def test_anonymous_user_cannot_delete_product(self):
        """비인증 사용자는 상품을 삭제할 수 없어야 한다."""
        response = self.client.delete(self.detail_url)

        self.assertIn(
            response.status_code,
            [status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN],
        )
        self.assertTrue(
            Product.objects.filter(pk=self.product.pk).exists()
        )

    def test_get_nonexistent_product_returns_404(self):
        """존재하지 않는 상품 조회 시 404를 반환해야 한다."""
        url = reverse(
            "product-detail",
            kwargs={"pk": 999999},
        )

        response = self.client.get(url)

        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

    def test_update_nonexistent_product_returns_404(self):
        """존재하지 않는 상품 수정 시 404를 반환해야 한다."""
        self.authenticate()

        url = reverse(
            "product-detail",
            kwargs={"pk": 999999},
        )

        response = self.client.patch(
            url,
            {"name": "Unknown Product"},
            format="json",
        )

        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

    def test_delete_nonexistent_product_returns_404(self):
        """존재하지 않는 상품 삭제 시 404를 반환해야 한다."""
        self.authenticate()

        url = reverse(
            "product-detail",
            kwargs={"pk": 999999},
        )

        response = self.client.delete(url)

        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

IsAuthenticatedOrReadOnly 권한만 사용하므로 인증된 사용자는 다른 사용자의 상품도 수정·삭제할 수 있습니다. 판매자 본인만 수정·삭제하도록 제한하려면 별도의 객체 권한 클래스가 필요합니다.

More in this category

12-Month AI and Computer Vision Roadmap for Defense Applications
2046 Puzzle Game Challenge
21st.dev component prompt
3D FACTORY
3D FPS Game