FastAPI教程 测试
感谢Starlette,测试FastAPI应用程序变得简单而愉快。
它基于Requests,因此非常熟悉和直观。
有了它,您可以直接将pytest与FastAPI一起使用。
使用 TestClient
进口TestClient。
创建一个TestClient传递给它的FastAPI应用程序。
创建名称以 开头的函数test_(这是标准pytest约定)。
TestClient以与使用相同的方式使用对象requests。
assert使用您需要检查的标准 Python 表达式编写简单的语句(再次,标准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"}
提示
请注意,测试功能是正常的def,而不是async def。
而且对客户端的调用也是普通调用,不是使用await.
这使您可以pytest直接使用而不会出现并发症。
技术细节
您也可以使用from starlette.testclient import TestClient.
FastAPI提供相同starlette.testclient的fastapi.testclient,就像为你的方便,开发人员。但它直接来自Starlette。
提示
如果async除了向 FastAPI 应用程序发送请求之外,还想调用测试中的函数(例如异步数据库函数),请查看高级教程中的异步测试。
分离测试
在实际应用程序中,您可能会将测试放在不同的文件中。
而且您的FastAPI应用程序也可能由多个文件/模块等组成。
FastAPI应用程序文件
假设您有一个main.py包含FastAPI应用程序的文件:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_main():
return {"msg": "Hello World"}
测试文件
然后你可以有一个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应用程序文件
假设您有一个main_b.py包含FastAPI应用程序的文件。
它有一个GET可能返回错误的操作。
它有一个POST可能返回多个错误的操作。
两个路径操作都需要一个X-Token标头。
from typing import Optional
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: Optional[str] = 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=400, detail="Item already exists")
fake_db[item.id] = item
return item
扩展测试文件
然后test_main_b.py,您可以像以前一样使用扩展测试:
from fastapi.testclient import TestClient
from .main_b 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_inexistent_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 == 400
assert response.json() == {"detail": "Item already exists"}
每当您需要客户端在请求中传递信息而您不知道如何传递时,您可以在requests.
然后你就在你的测试中做同样的事情。
例如:
- 要传递路径或查询参数,请将其添加到 URL 本身。
- 要传递 JSON 正文,dict请将Python 对象(例如 a )传递给参数json。
- 如果您需要发送表单数据而不是 JSON,请改用data参数。
- 要传递headers,请dict在headers参数中使用 a 。
- 对于饼干,一个dict在cookies参数。
有关如何将数据传递到后端(使用requests或TestClient)的更多信息,请查看请求文档。
信息
请注意,TestClient接收可以转换为 JSON 的数据,而不是 Pydantic 模型。
如果您的测试中有 Pydantic 模型,并且您想在测试期间将其数据发送到应用程序,则可以使用JSON Compatible Encoder 中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 ...... [100%]
================= 1 passed in 0.03s =================