【Django】APIキーを利用したPOSTリクエストをテストするためのテストケースの作成方法

# 概要
* djangoのテストフレームワークでAPIキーを使用したPOSTリクエストのテストケースを作成しました。ヘッダーの`HTTP_`プレフィックスとテストクライアントの`post`メソッドの引数`headers`あたりで詰まったところも調べてまとめておきましたので、これからAPIテスト作る方のご参考となればと思います。


## 前提
* Python：3.6.15
* Django：3.0.5

# APIのテストケース（サンプルコード）
* 以下が`HTTP_X-API-KEY`を使用したDjangoのサンプルコードです。
    * 正しいAPIキーは`SAMPLE_API_KEY`とします。
    * 間違ったAPIキーを`WRONG_API_KEY`とします。
* 以下のテストケースでは、正しいAPIキーと間違ったAPIキーを使用してリクエストを送信し、レスポンスのステータスコードを検証しています。

```python
from django.test import TestCase

class ApiTestCase(TestCase):
    def test_post_sample_with_api_key(self):
        headers = {'HTTP_X_API_KEY': 'SAMPLE_API_KEY'}
        data = {'key': 'value'}
        response = self.client.post('/api/', data=data, follow=True, content_type='application/json', accept='application/json', **headers)

        self.assertEqual(response.status_code, 200)

    def test_sample_without_api_key(self):
        headers = {'HTTP_X_API_KEY': 'WRONG_API_KEY'}
        data = {'key': 'value'}
        response = self.client.post('/api/', data=data, follow=True, content_type='application/json', accept='application/json', **headers)

        self.assertEqual(response.status_code, 401)
```

## リクエストヘッダーについて
* ①ヘッダー名に`HTTP_`プレフィックスをつける必要がある
* ②`headers`は`post`のキーワード引数として指定する必要がある
* ③そのほかのヘッダー部分の説明

### ①ヘッダー名に`HTTP_`プレフィックスをつける必要がある
* ちょっと詰まったのが、リクエストヘッダーの`X-API-KEY`です。APIのHTTPリクエストにはヘッダーとボディがありますが、`X-API-KEY`はヘッダー部分になります。
* `HTTP_`プリフィックスは、HTTPリクエストヘッダーのキーを指定するために使用されます。Djangoのテストクライアントでは、ヘッダーをキーワード引数として渡す場合に、ヘッダー名に`HTTP_`プリフィックスを付ける必要があるのです。これがわからずに以下のように記載していたのですが、全然成功しないので結構詰まりました。

```python
headers = {'X_API_KEY': 'SAMPLE_API_KEY'}
```

* `HTTP_`プリフィックスは、Djangoのテストクライアントが実際のHTTPリクエストをシミュレートするために、環境変数としてヘッダーを設定しているかららしい。確かに以下のサイトを見ても`USER_AGENT`などに`HTTP_`プリフィックスを付けていますね。
    * [Django 4.0.6 ドキュメント](https://man.plustar.jp/django/topics/testing/tools.html)
* この辺はあまり記事がないように感じました。以下が近かったです。
    * [DjangoやDRFのテストクライアントにヘッダーを設定する方法](https://djangobrothers.com/blogs/django_drf_test_client_header/)

### ②`headers`は`post`のキーワード引数として指定する必要がある
* `**headers`は辞書として定義されたキーワード引数を展開するPythonの構文で、上記の場合は以下のような形で展開されます。

```python
    self.client.post('/api/', data=data, follow=True, content_type='application/json', accept='application/json', HTTP_X_API_KEY='SAMPLE_API_KEY')
```

* この`headers`と先述の`HTTP_`が相まって詰まりました。というのも、`**headers`であれば成功しましたが`headers=headers`だと失敗してしまったからです。Djangoのテストクライアントの`post`メソッドでは、引数にキーワード引数を利用する必要があるのです。以下が公式ドキュメントからの引用です。
    * [Testing Django Applications - Making requests](https://docs.djangoproject.com/en/3.2/topics/testing/tools/#django.test.Client)


>It requires no arguments at time of construction. However, you can use keyword arguments to specify some default headers.

* キーワード引数を使用してリクエストで送信するヘッダを指定する、とありますね。なるほど。`headers`を使うことで、実質的に辞書を解凍し、各キーと値のペアを別々のキーワード引数として`post()`メソッドに渡していることになるんですね。
* Djangoの`self.client.post()`メソッドでは、メソッドのシグネチャは次のようになります。`**extra`パラメータは、リクエストのHTTPヘッダとして扱われる追加のキーワード引数を渡すことができます。

```python
def post(self, path, data=None, content_type='application/octet-stream', follow=False, secure=False, **extra)：
```

* ということで、今回の大きな学びは以下の二つ。
    * `self.client.post()`メソッドを使う場合、HTTP ヘッダは`'HTTP_'`を先頭につけて大文字に変換する必要があること
    * `self.client.post()`メソッドを使ってリクエストを行う場合、ヘッダーは辞書としてではなく、キーワード引数として渡す必要があること。
* これで、`x-api-key`ヘッダーを指定する際に、キーワード引数の`headers`辞書のキーとして`HTTP_X_API_KEY`を使用することで無事にテストすることができました。

### ③そのほかのヘッダー部分の説明
* `self.client.post`を使用して`/api/`へのPOSTリクエストをシミュレートします。
* `follow=True`：リダイレクトを追跡する設定になります。
* `content_type='application/json'`：リクエストのコンテンツタイプはJSONを指定していることを示しています。
    * `content_type`を指定しない場合、データは`multipart/form-data`のコンテンツタイプで送信されます。
* `accept='application/json'`：JSON形式のレスポンスを返すことを期待していることを示しています。
* 他にも、`secure=True`にすると、HTTPSリクエストをエミュレートしたりできます。
* 今回は`self.client.post()`メソッドを使用してPOSTリクエストを送信していますが、他のHTTPメソッド（GET、PUT、DELETEなど）も同様に使用することができます。必要に応じて、適切なメソッドを選択してテストケースを作成しましょう。この辺は上述の公式ドキュメントにも詳細に記載があります。

## リクエストボディとアサーションについて
* リクエストボディについては`data = {'key': 'value'}`が該当します。データとして`data`変数に`{'key': 'value'}`という辞書が定義されています。実際は、`data`変数に適切なデータを設定し、期待される振る舞いをテストすることになります。
* テストケース内の`response`オブジェクトには、APIからのレスポンスに関する情報が含まれています。
* Pythonではアサーションを使用して、レスポンスのステータスコードが`200`であることをテストできます。それが以下のコードです。

```py
self.assertEqual(response.status_code, 200)
```

* 一つ目では`SAMPLE_API_KEY`という正しいAPIキーが入っているのでステータスコード`200`でテストに成功します。
* 二つ目では、`WRONG_API_KEY`という間違ったAPIキーが入っているので、ステータスコード`401`でテストに成功します(任意のエラーステータスとエラーメッセージを返すようにしている場合は都度ここを変える必要あり)。