Skip to content

🔬

👏 💃, 🔬 FastAPI 🈸 ⏩ & 😌.

⚫️ ⚓️ 🔛 🇸🇲, ❔ 🔄 🏗 ⚓️ 🔛 📨, ⚫️ 📶 😰 & 🏋️.

⏮️ ⚫️, 👆 💪 ⚙️ ✳ 🔗 ⏮️ FastAPI.

⚙️ TestClient

Info

⚙️ TestClient, 🥇 ❎ httpx.

🤶 Ⓜ. pip install httpx.

🗄 TestClient.

✍ TestClient 🚶‍♀️ 👆 FastAPI 🈸 ⚫️.

✍ 🔢 ⏮️ 📛 👈 ▶️ ⏮️ test_ (👉 🐩 pytest 🏛).

⚙️ TestClient 🎚 🎏 🌌 👆 ⏮️ httpx.

✍ 🙅 assert 📄 ⏮️ 🐩 🐍 🧬 👈 👆 💪 ✅ (🔄, 🐩 pytest).

from fastapi import FastAPI
from fastapi.testclient import TestClient

app = FastAPI()


@app.get("/")
async def read_main():
    return {"msg": "Hello World"}


client = TestClient(app)


def test_read_main():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"msg": "Hello World"}

Tip

👀 👈 🔬 🔢 😐 def, 🚫 async def.

& 🤙 👩‍💻 😐 🤙, 🚫 ⚙️ await.

👉 ✔ 👆 ⚙️ pytest 🔗 🍵 🤢.

📡 ℹ

👆 💪 ⚙️ from starlette.testclient import TestClient.

FastAPI 🚚 🎏 starlette.testclient fastapi.testclient 🏪 👆, 👩‍💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃.

Tip

🚥 👆 💚 🤙 async 🔢 👆 💯 ↖️ ⚪️➡️ 📨 📨 👆 FastAPI 🈸 (✅ 🔁 💽 🔢), ✔️ 👀 🔁 💯 🏧 🔰.

🎏 💯

🎰 🈸, 👆 🎲 🔜 ✔️ 👆 💯 🎏 📁.

& 👆 FastAPI 🈸 5️⃣📆 ✍ 📚 📁/🕹, ♒️.

FastAPI 📱 📁

➡️ 💬 👆 ✔️ 📁 📊 🔬 🦏 🈸:

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

📁 main.py 👆 ✔️ 👆 FastAPI 📱:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def read_main():
    return {"msg": "Hello World"}

🔬 📁

⤴️ 👆 💪 ✔️ 📁 test_main.py ⏮️ 👆 💯. ⚫️ 💪 🖖 🔛 🎏 🐍 📦 (🎏 📁 ⏮️ __init__.py 📁):

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

↩️ 👉 📁 🎏 📦, 👆 💪 ⚙️ ⚖ 🗄 🗄 🎚 app ⚪️➡️ main 🕹 (main.py):

from fastapi.testclient import TestClient

from .main import app

client = TestClient(app)


def test_read_main():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"msg": "Hello World"}

...& ✔️ 📟 💯 💖 ⏭.

🔬: ↔ 🖼

🔜 ➡️ ↔ 👉 🖼 & 🚮 🌖 ℹ 👀 ❔ 💯 🎏 🍕.

↔ FastAPI 📱 📁

➡️ 😣 ⏮️ 🎏 📁 📊 ⏭:

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

➡️ 💬 👈 🔜 📁 main.py ⏮️ 👆 FastAPI 📱 ✔️ 🎏 ➡ 🛠️.

⚫️ ✔️ GET 🛠️ 👈 💪 📨 ❌.

⚫️ ✔️ POST 🛠️ 👈 💪 📨 📚 ❌.

👯‍♂️ ➡ 🛠️ 🚚 X-Token 🎚.

from typing import Union

from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel

fake_secret_token = "coneofsilence"

fake_db = {
    "foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
    "bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
}

app = FastAPI()


class Item(BaseModel):
    id: str
    title: str
    description: Union[str, None] = None


@app.get("/items/{item_id}", response_model=Item)
async def read_main(item_id: str, x_token: str = Header()):
    if x_token != fake_secret_token:
        raise HTTPException(status_code=400, detail="Invalid X-Token header")
    if item_id not in fake_db:
        raise HTTPException(status_code=404, detail="Item not found")
    return fake_db[item_id]


@app.post("/items/", response_model=Item)
async def create_item(item: Item, x_token: str = Header()):
    if x_token != fake_secret_token:
        raise HTTPException(status_code=400, detail="Invalid X-Token header")
    if item.id in fake_db:
        raise HTTPException(status_code=409, detail="Item already exists")
    fake_db[item.id] = item
    return item
from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel

fake_secret_token = "coneofsilence"

fake_db = {
    "foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
    "bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
}

app = FastAPI()


class Item(BaseModel):
    id: str
    title: str
    description: str | None = None


@app.get("/items/{item_id}", response_model=Item)
async def read_main(item_id: str, x_token: str = Header()):
    if x_token != fake_secret_token:
        raise HTTPException(status_code=400, detail="Invalid X-Token header")
    if item_id not in fake_db:
        raise HTTPException(status_code=404, detail="Item not found")
    return fake_db[item_id]


@app.post("/items/", response_model=Item)
async def create_item(item: Item, x_token: str = Header()):
    if x_token != fake_secret_token:
        raise HTTPException(status_code=400, detail="Invalid X-Token header")
    if item.id in fake_db:
        raise HTTPException(status_code=409, detail="Item already exists")
    fake_db[item.id] = item
    return item

↔ 🔬 📁

👆 💪 ⤴️ ℹ test_main.py ⏮️ ↔ 💯:

from fastapi.testclient import TestClient

from .main import app

client = TestClient(app)


def test_read_item():
    response = client.get("/items/foo", headers={"X-Token": "coneofsilence"})
    assert response.status_code == 200
    assert response.json() == {
        "id": "foo",
        "title": "Foo",
        "description": "There goes my hero",
    }


def test_read_item_bad_token():
    response = client.get("/items/foo", headers={"X-Token": "hailhydra"})
    assert response.status_code == 400
    assert response.json() == {"detail": "Invalid X-Token header"}


def test_read_nonexistent_item():
    response = client.get("/items/baz", headers={"X-Token": "coneofsilence"})
    assert response.status_code == 404
    assert response.json() == {"detail": "Item not found"}


def test_create_item():
    response = client.post(
        "/items/",
        headers={"X-Token": "coneofsilence"},
        json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"},
    )
    assert response.status_code == 200
    assert response.json() == {
        "id": "foobar",
        "title": "Foo Bar",
        "description": "The Foo Barters",
    }


def test_create_item_bad_token():
    response = client.post(
        "/items/",
        headers={"X-Token": "hailhydra"},
        json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"},
    )
    assert response.status_code == 400
    assert response.json() == {"detail": "Invalid X-Token header"}


def test_create_existing_item():
    response = client.post(
        "/items/",
        headers={"X-Token": "coneofsilence"},
        json={
            "id": "foo",
            "title": "The Foo ID Stealers",
            "description": "There goes my stealer",
        },
    )
    assert response.status_code == 409
    assert response.json() == {"detail": "Item already exists"}

🕐❔ 👆 💪 👩‍💻 🚶‍♀️ ℹ 📨 & 👆 🚫 💭 ❔, 👆 💪 🔎 (🇺🇸🔍) ❔ ⚫️ httpx, ⚖️ ❔ ⚫️ ⏮️ requests, 🇸🇲 🔧 ⚓️ 🔛 📨' 🔧.

⤴️ 👆 🎏 👆 💯.

🤶 Ⓜ.:

  • 🚶‍♀️ ➡ ⚖️ 🔢 🔢, 🚮 ⚫️ 📛 ⚫️.
  • 🚶‍♀️ 🎻 💪, 🚶‍♀️ 🐍 🎚 (✅ dict) 🔢 json.
  • 🚥 👆 💪 📨 📨 💽 ↩️ 🎻, ⚙️ data 🔢 ↩️.
  • 🚶‍♀️ 🎚, ⚙️ dict headers 🔢.
  • 🍪, dict cookies 🔢.

🌖 ℹ 🔃 ❔ 🚶‍♀️ 💽 👩‍💻 (⚙️ httpx ⚖️ TestClient) ✅ 🇸🇲 🧾.

Info

🗒 👈 TestClient 📨 💽 👈 💪 🗜 🎻, 🚫 Pydantic 🏷.

🚥 👆 ✔️ Pydantic 🏷 👆 💯 & 👆 💚 📨 🚮 💽 🈸 ⏮️ 🔬, 👆 💪 ⚙️ jsonable_encoder 🔬 🎻 🔗 🔢.

🏃 ⚫️

⏮️ 👈, 👆 💪 ❎ pytest:

$ pip install pytest

---> 100%

⚫️ 🔜 🔍 📁 & 💯 🔁, 🛠️ 👫, & 📄 🏁 🔙 👆.

🏃 💯 ⏮️:

$ pytest

================ test session starts ================
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: /home/user/code/superawesome-cli/app
plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1
collected 6 items

---> 100%

test_main.py <span style="color: green; white-space: pre;">......                            [100%]</span>

<span style="color: green;">================= 1 passed in 0.03s =================</span>