[Python]FastAPIテスト方法 メモ

* Python WebAPI開発用フレームワークFastAPIを使用して作成したAPIのテスト方法についてメモする。



## 大まかなテストコードの書き方

1. `TestClient` をインポートする。

2. `TestClient` を作成し、FastAPIに渡す。

3. `test_` 始まりの名前の関数を作成する (`pytest` 仕様)。

4. `TestClient` オブジェクトを使用し、テスト用リクエストを記述する。
5. チェック内容の `assert` 文を記述する。

## 事前準備

* `pytest`インストール

  ```shell
  pip install pytest
  ```

  

## コード

### テスト対象

* `main.py`
  * ユーザー情報の登録・取得を行うAPI

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

# 正常トークン
correct_token = "correct_token"

# 模擬ユーザーデータ
dummy_user_db = {
    "abcde12345": {"id": "abcde12345", "name": "Yamada", "email_address": "yamada@example.com"},
    "fghij67890": {"id": "fghij67890", "name": "Tanaka", "email_address": "tanaka@example.com"},
}

app = FastAPI()


class User(BaseModel):
    id: str
    name: str
    email_address: str

# ユーザー情報取得API
@app.get("/users/{user_id}", response_model=User)
async def get_user(user_id: str, token: str = Header(...)):
    # トークン不正時
    if token != correct_token:
        raise HTTPException(
            status_code=400, detail="token_verification_failed")
    # ユーザー非存在時
    if user_id not in dummy_user_db:
        raise HTTPException(status_code=404, detail="user_not_found")
    return dummy_user_db[user_id]


# ユーザー情報登録API
@app.post("/users/", response_model=User)
async def create_user(user: User, token: str = Header(...)):
    # トークン不正時
    if token != correct_token:
        raise HTTPException(
            status_code=400, detail="token_verification_failed")
    # ユーザーID重複時
    if user.id in dummy_user_db:
        raise HTTPException(status_code=400, detail="user_id_duplicated")
    dummy_user_db[user.id] = user
    return user

```



### テストコード

* 各APIの正常/異常系リクエスト時のレスポンスを検証する。

```python
from fastapi.testclient import TestClient

from main import app

client = TestClient(app)


# ユーザー情報取得API 正常系テスト
def test_get_user_NR001():
    response = client.get(
        "/users/abcde12345", headers={"token": "correct_token"})
    # レスポンス検証
    # ステータスコード
    assert response.status_code == 200
    # レスポンスボディ
    assert response.json() == {
        "id": "abcde12345",
        "name": "Yamada",
        "email_address": "yamada@example.com",
    }

# ユーザー情報取得API 異常系テスト
# トークン違い
def test_get_user_ABR001():
    response = client.get("/users/abcde12345",
                          headers={"token": "dummy_token"})
    assert response.status_code == 400
    assert response.json() == {"detail": "token_verification_failed"}

# ユーザー情報取得API 異常系テスト
# 存在しないユーザー指定
def test_get_user_ABR002():
    response = client.get(
        "/users/hogefuga", headers={"token": "correct_token"})
    assert response.status_code == 404
    assert response.json() == {"detail": "user_not_found"}

# ユーザー情報登録API 正常系テスト
def test_create_user_NR001():
    # 正常系リクエスト
    # ヘッダーとボディは辞書型で渡す。
    response = client.post(
        "/users/",
        headers={"Token": "correct_token"},
        json={"id": "klmno12345", "name": "Kobayashi",
              "email_address": "kobayashi@example.com"},
    )
    assert response.status_code == 200
    assert response.json() == {
        "id": "klmno12345",
        "name": "Kobayashi",
        "email_address": "kobayashi@example.com",
    }

# ユーザー情報登録API 異常系テスト
# トークン違い
def test_create_user_ABR001():
    response = client.post(
        "/users/",
        headers={"Token": "dummy_token"},
        json={"id": "klmno12345", "name": "Kobayashi",
              "email_address": "kobayashi@example.com"},
    )
    assert response.status_code == 400
    assert response.json() == {"detail": "token_verification_failed"}


# ユーザー情報登録API 異常系テスト
# 登録済みユーザーID指定
def test_create_user_ABR002():
    response = client.post(
        "/users/",
        headers={"Token": "correct_token"},
        json={
            "id": "abcde12345",
            "name": "Kobayashi",
            "email_address": "kobayashi@example.com"
        },
    )
    assert response.status_code == 400
    assert response.json() == {"detail": "user_id_duplicated"}

```



## 動作確認

```shell
pytest
================================================================ test session starts ================================================================
platform win32 -- Python 3.8.3, pytest-6.2.3, py-1.10.0, pluggy-0.13.1
rootdir: C:\Users\user\Documents\dev\vscode\ws_git\fastapi\test
plugins: anyio-3.2.1, cov-2.11.1
collected 6 items

test_main.py ......                                                                                                                            [100%]

================================================================= 6 passed in 0.56s =================================================================
```



## 参考情報

* [FastAPI - テスト](https://fastapi.tiangolo.com/ja/tutorial/testing/)