Ana içeriğe geç

Async Testler

🌐 Translation by AI and humans

This translation was made by AI guided by humans. 🤝

It could have mistakes of misunderstanding the original meaning, or looking unnatural, etc. 🤖

You can improve this translation by helping us guide the AI LLM better.

English version

Sağlanan TestClient ile FastAPI uygulamalarınızı nasıl test edeceğinizi zaten gördünüz. Şimdiye kadar yalnızca senkron testler yazdık, yani async fonksiyonlar kullanmadan.

Testlerinizde asenkron fonksiyonlar kullanabilmek faydalı olabilir; örneğin veritabanınızı asenkron olarak sorguluyorsanız. Diyelim ki FastAPI uygulamanıza request gönderilmesini test etmek ve ardından async bir veritabanı kütüphanesi kullanırken backend'in doğru veriyi veritabanına başarıyla yazdığını doğrulamak istiyorsunuz.

Bunu nasıl çalıştırabileceğimize bir bakalım.

pytest.mark.anyio

Testlerimizde asenkron fonksiyonlar çağırmak istiyorsak, test fonksiyonlarımızın da asenkron olması gerekir. AnyIO bunun için güzel bir plugin sağlar; böylece bazı test fonksiyonlarının asenkron olarak çağrılacağını belirtebiliriz.

HTTPX

FastAPI uygulamanız async def yerine normal def fonksiyonları kullanıyor olsa bile, altta yatan yapı hâlâ bir async uygulamadır.

TestClient, standart pytest kullanarak normal def test fonksiyonlarınızın içinden asenkron FastAPI uygulamasını çağırmak için içeride bazı “sihirli” işlemler yapar. Ancak bu sihir, onu asenkron fonksiyonların içinde kullandığımızda artık çalışmaz. Testlerimizi asenkron çalıştırdığımızda, test fonksiyonlarımızın içinde TestClient kullanamayız.

TestClient, HTTPX tabanlıdır ve neyse ki API'yi test etmek için HTTPX'i doğrudan kullanabiliriz.

Örnek

Basit bir örnek için, Bigger Applications ve Testing bölümlerinde anlatılana benzer bir dosya yapısı düşünelim:

.
├── app
│   ├── __init__.py
│   ├── main.py
│   └── test_main.py

main.py dosyası şöyle olur:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Tomato"}

test_main.py dosyasında main.py için testler yer alır, artık şöyle görünebilir:

import pytest
from httpx import ASGITransport, AsyncClient

from .main import app


@pytest.mark.anyio
async def test_root():
    async with AsyncClient(
        transport=ASGITransport(app=app), base_url="http://test"
    ) as ac:
        response = await ac.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Tomato"}

Çalıştırma

Testlerinizi her zamanki gibi şu şekilde çalıştırabilirsiniz:

$ pytest

---> 100%

Detaylı Anlatım

@pytest.mark.anyio marker'ı, pytest'e bu test fonksiyonunun asenkron olarak çağrılması gerektiğini söyler:

import pytest
from httpx import ASGITransport, AsyncClient

from .main import app


@pytest.mark.anyio
async def test_root():
    async with AsyncClient(
        transport=ASGITransport(app=app), base_url="http://test"
    ) as ac:
        response = await ac.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Tomato"}

İpucu

Test fonksiyonu artık TestClient kullanırken eskiden olduğu gibi sadece def değil, async def.

Ardından app ile bir AsyncClient oluşturup await kullanarak ona async request'ler gönderebiliriz.

import pytest
from httpx import ASGITransport, AsyncClient

from .main import app


@pytest.mark.anyio
async def test_root():
    async with AsyncClient(
        transport=ASGITransport(app=app), base_url="http://test"
    ) as ac:
        response = await ac.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Tomato"}

Bu, şu kullanıma denktir:

response = client.get('/')

...ki daha önce request'leri TestClient ile bu şekilde gönderiyorduk.

İpucu

Yeni AsyncClient ile async/await kullandığımızı unutmayın; request asenkron çalışır.

Uyarı

Uygulamanız lifespan event'lerine dayanıyorsa, AsyncClient bu event'leri tetiklemez. Tetiklendiklerinden emin olmak için florimondmanca/asgi-lifespan paketindeki LifespanManager'ı kullanın.

Diğer Asenkron Fonksiyon Çağrıları

Test fonksiyonu artık asenkron olduğundan, testlerinizde FastAPI uygulamanıza request göndermenin yanında başka async fonksiyonları da (çağırıp await ederek) kodunuzun başka yerlerinde yaptığınız gibi aynı şekilde kullanabilirsiniz.

İpucu

Testlerinize asenkron fonksiyon çağrıları entegre ederken RuntimeError: Task attached to a different loop hatasıyla karşılaşırsanız (ör. MongoDB'nin MotorClient kullanımı), event loop gerektiren nesneleri yalnızca async fonksiyonların içinde oluşturmanız gerektiğini unutmayın; örneğin bir @app.on_event("startup") callback'i içinde.