# 非同步測試 { #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")` 回呼中。
///