# 非同步測試 { #async-tests } 你已經看過如何使用提供的 `TestClient` 來測試你的 FastAPI 應用。到目前為止,你只看到如何撰寫同步測試,沒有使用 `async` 函式。 在測試中能使用非同步函式會很有用,例如當你以非同步方式查詢資料庫時。想像你想測試發送請求到 FastAPI 應用,然後在使用非同步資料庫函式庫時,驗證後端是否成功把正確資料寫入資料庫。 來看看怎麼做。 ## pytest.mark.anyio { #pytest-mark-anyio } 若要在測試中呼叫非同步函式,測試函式本身也必須是非同步的。AnyIO 為此提供了一個好用的外掛,讓我們可以標示某些測試函式以非同步方式執行。 ## HTTPX { #httpx } 即使你的 FastAPI 應用使用一般的 `def` 函式而非 `async def`,它在底層仍然是個 `async` 應用。 `TestClient` 在內部做了一些魔法,讓我們能在一般的 `def` 測試函式中,使用標準 pytest 來呼叫非同步的 FastAPI 應用。但當我們在非同步函式中使用它時,這個魔法就不再奏效了。也就是說,當以非同步方式執行測試時,就不能在測試函式內使用 `TestClient`。 `TestClient` 是建立在 [HTTPX](https://www.python-httpx.org) 之上,所幸我們可以直接使用它來測試 API。 ## 範例 { #example } 作為簡單範例,讓我們考慮與[更大型的應用](../tutorial/bigger-applications.md)與[測試](../tutorial/testing.md)中描述的類似檔案結構: ``` . ├── app │   ├── __init__.py │   ├── main.py │   └── test_main.py ``` 檔案 `main.py` 會是: {* ../../docs_src/async_tests/app_a_py310/main.py *} 檔案 `test_main.py` 會包含針對 `main.py` 的測試,現在可能像這樣: {* ../../docs_src/async_tests/app_a_py310/test_main.py *} ## 執行 { #run-it } 如常執行測試:
```console $ pytest ---> 100% ```
## 詳解 { #in-detail } 標記 `@pytest.mark.anyio` 告訴 pytest 這個測試函式應以非同步方式執行: {* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *} /// tip 注意,測試函式現在是 `async def`,而不是像使用 `TestClient` 時那樣僅用 `def`。 /// 接著,我們可以用該應用建立 `AsyncClient`,並以 `await` 發送非同步請求。 {* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *} 這等同於: ```Python response = client.get('/') ``` 也就是先前用 `TestClient` 發送請求時所用的寫法。 /// tip 注意,對新的 `AsyncClient` 需搭配 async/await —— 請求是非同步的。 /// /// warning 如果你的應用仰賴 lifespan 事件,`AsyncClient` 不會觸發這些事件。若要確保它們被觸發,請使用 [florimondmanca/asgi-lifespan](https://github.com/florimondmanca/asgi-lifespan#usage) 的 `LifespanManager`。 /// ## 其他非同步函式呼叫 { #other-asynchronous-function-calls } 由於測試函式現在是非同步的,你也可以在測試中呼叫(並 `await`)其他 `async` 函式,和在程式碼其他地方一樣。 /// tip 如果在將非同步呼叫整合進測試時遇到 `RuntimeError: Task attached to a different loop`(例如使用 [MongoDB 的 MotorClient](https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop) 時),請記得:需要事件迴圈的物件只應在非同步函式內實例化,例如在 `@app.on_event("startup")` 回呼中。 ///