Asosiy kontentga o‘tish

Maqolalar

Darslik · 2026-08-16 · ~7 daqiqa o‘qiladi · boshlang'ich

DRF API'ni APIClient bilan testlash: status, JSON va ruxsatlar

#python #django #drf #testing

Mundarija

Seriyaning birinchi va ikkinchi qismlarida oddiy Django view'largacha yetib keldik. Endi navbat API'da. REST API yozgan odamning odatiy ish tartibi tanish: kod yozdi, Postman'ni ochdi, so'rov yubordi, javobga qaradi. Bu ham aslida "brauzerda bosib ko'rish"ning o'zi — faqat asbob boshqa. Va davosi ham o'sha: bosishni kod qilib yozib qo'yamiz.

Misol sifatida ToDo loyihasidagi API'ning soddalashtirilgan ko'rinishini olamiz:

# apps/todos/serializers.py
from rest_framework import serializers

from apps.todos.models import Task


class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = ["id", "title", "is_done", "due_date"]
# apps/todos/api.py
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated

from apps.todos.models import Task
from apps.todos.serializers import TaskSerializer


class TaskViewSet(viewsets.ModelViewSet):
    serializer_class = TaskSerializer
    permission_classes = [IsAuthenticated]  # API faqat kirgan foydalanuvchiga

    def get_queryset(self):
        # har kim faqat o'z vazifalarini ko'radi
        return Task.objects.filter(owner=self.request.user)

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

APIClient: oddiy client'dan nimasi ortiq#

pytest-django bergan client fixture'i HTML sahifalar uchun qulay edi. API uchun DRF o'zining APIClientini beradi: JSON'ni o'zi seriyalaydi, format="json" bilan Content-Type'ni to'g'rilaydi va eng foydalisi — force_authenticate orqali login jarayonisiz "kirgan foydalanuvchi" holatini beradi. Uni bir marta fixture qilib olamiz, hamma test foydalanadi:

# apps/todos/tests/conftest.py
import pytest
from django.contrib.auth.models import User
from rest_framework.test import APIClient


@pytest.fixture
def user(db):
    return User.objects.create_user("olim", password="sinov12345")


@pytest.fixture
def api(user):
    """Login qilgan holdagi API mijoz — testlar tayyor holda oladi."""
    client = APIClient()
    # sessiya/token bilan ovora bo'lmaymiz: autentifikatsiyani testlash
    # alohida mavzu, bu yerda esa API mantig'ini tekshiryapmiz
    client.force_authenticate(user=user)
    return client

conftest.py — pytest'ning umumiy fixture ombori: shu papkadagi barcha testlar user va apini parametr sifatida so'rab olaveradi.

To'rt asosiy stsenariy#

API testlari odatda to'rt savol atrofida aylanadi: to'g'ri so'rov to'g'ri javob oladimi, yaroqsiz so'rov to'g'ri rad etiladimi, begona kirolmaydimi va ma'lumot chegarasi buzilmaydimi. Har biriga bittadan yozamiz:

# apps/todos/tests/test_api.py
import pytest
from django.contrib.auth.models import User
from rest_framework.test import APIClient

from apps.todos.models import Task


def test_vazifa_yaratiladi(api):
    response = api.post("/api/tasks/", {"title": "Maqola yozish"}, format="json")

    assert response.status_code == 201
    data = response.json()
    assert data["title"] == "Maqola yozish"
    assert data["is_done"] is False  # default qiymat ham javobda to'g'ri
    assert Task.objects.count() == 1  # bazaga haqiqatan yozildi


def test_bosh_sarlavha_rad_etiladi(api):
    response = api.post("/api/tasks/", {"title": ""}, format="json")

    assert response.status_code == 400
    # xato aynan qaysi maydonda ekani ham javobda aytilgan bo'lishi kerak —
    # mobil/frontend jamoasi shu maydonga qarab xabar ko'rsatadi
    assert "title" in response.json()
    assert Task.objects.count() == 0


def test_kirmagan_foydalanuvchi_401_oladi(db):
    anonim = APIClient()  # force_authenticate YO'Q
    response = anonim.get("/api/tasks/")
    assert response.status_code == 401


def test_begona_vazifa_korinmaydi(api, db):
    begona = User.objects.create_user("g'ani")
    Task.objects.create(title="Begonaning ishi", owner=begona)

    response = api.get("/api/tasks/")

    assert response.status_code == 200
    assert response.json() == []  # ro'yxat bo'sh — begonaniki chiqmadi

Oxirgi test alohida e'tiborga loyiq. get_querysetdagi filter(owner=...) qatori — xavfsizlik chegarasi, va bunday chegara har doim testda mustahkamlab qo'yilishi kerak: ertaga kimdir refactoring paytida o'sha filtrni yo'qotib qo'ysa, xato jimgina o'tib ketmaydi — mana shu test qizarib turadi. API'dagi bunday "meniki-seniki" xatolari eng ko'p uchraydigan zaifliklardan biri, bu haqda OWASP maqolasida ham yozganman.

Yiqilganda nimani o'qiysiz#

Test yiqilsa, pytest javobning o'zini ham ko'rsatadi — lekin unga bitta yordam qo'shsangiz, hayot yana osonlashadi:

    assert response.status_code == 201, response.json()

Assert'ning ikkinchi argumenti xato xabariga qo'shiladi: status kutilgancha chiqmasa, terminalda serializer'ning to'liq xato javobini ko'rasiz — "400 keldi" degan quruq faktni emas, aynan nima noto'g'ri ekanini.

Nomlash bilan hujjat

E'tibor bergan bo'lsangiz, test nomlari yana gap kabi: yaratiladi, rad etiladi, ko'rinmaydi. To'plam kattalashganda pytest --collect-only chiqishi o'z-o'zidan API'ning xulq-atvor hujjatiga aylanadi — qaysi holatda nima bo'lishi ro'yxat bo'lib turadi.

Yakun — va seriya xulosasi#

APIClient bilan test yozish oddiy client'dan deyarli farq qilmaydi, lekin API dunyosiga mos uch odat qo'shiladi: statusni tekshirish, JSON tanasini tekshirish va ruxsat chegaralarini tekshirish. Uchinchisi eng muhimi — funksionallik buzilsa foydalanuvchi shikoyat qiladi, xavfsizlik buzilsa hech kim indamaydi.

Uch qismlik testlash seriyasi shu yerda yakunlanadi: birinchi testdan boshladik, unit va integration muvozanatini ko'rdik, API'ni qamrab oldik. Bu bilimlarning jonli namunasi — ToDo loyihasi, test intizomini o'rganish uchun ataylab qurilgan maydon.