Django REST frameworkのユニットテスト
Tags: django, python
Published: 2022-01-20 10:37:02

Django REST framework（以下DRF）のテストについて軽くまとめておきます。
一応前回の続きという事で進めて行きます。
正直Djangoのテストは慣れていないのでなにからやれば良いか試行錯誤している段階ではあります。

## テストフォルダの作成
DRFの場合、テスト対象としてはアプリケーション毎にモデル、シリアライザ、ビューの3種類が考えられます。
`startapp`で自動生成される`apiv1/tests.py`にテストを書いていってもいい気もしますが、上記3種のテストを1ファイルに書いてしまうと肥大化していくのは目に見えているので、上記を分離しておきます。
`apiv1/tests`フォルダを作成して

- `apiv1/tests/__init__.py`
- `apiv1/tests/test_models.py`
- `apiv1/tests/test_serializers.py`
- `apiv1/tests/test_views.py`

の用にテストファイルを作成します。（必要なものだけで大丈夫かと思います。）
`__init__.py`は空ファイルで大丈夫です。ついでに

- `apiv1/tests.py`

は削除しておきます。

## ビューのテスト
今回はDRFのviewsetsをそのまま継承して利用しているだけなので、おそらくテストは不要だと思います。
とはいえ一応チュートリアル的な観点で試したいのでTodoの生成だけテストを書いておきます。`apiv1/tests/test_views.py`を作成します。

```python
from django.urls import reverse
from rest_framework.test import APITestCase


from todos.models import Todo


class TodoCreateTests(APITestCase):
    def test_create_success(self):
        params = {
            "title": "test_create_success",
        }
        url = reverse("apiv1:todos-list")
        response = self.client.post(url, params, format="json")

        self.assertEqual(Todo.objects.count(), 1)
        self.assertEqual(response.status_code, 201)
        todo = Todo.objects.get()
        expected_json_dict = {
            "id": str(todo.id),
            "title": todo.title,
            "checked": todo.checked,
        }
        self.assertJSONEqual(response.content, expected_json_dict)

    def test_create_bad_request(self):
        params = {
            "title": "",
        }
        url = reverse("apiv1:todos-list")
        response = self.client.post(url, params, format="json")

        self.assertEqual(Todo.objects.count(), 0)
        self.assertEqual(response.status_code, 400)

```

下記コマンドで全テストが捜索され実行されます。

```ps
python manage.py test
```

アプリケーションやクラスを指定して実行もできますが、ここでは割愛します。

## シリアライザのテスト
シリアライザのテストは、実際のリクエストを受け入れる際のバリデーションと、レスポンスを返す時の出力内容を検証すれば大丈夫だと思います。引き続き、`apiv1/tests/test_serializers.py`を作成します。

```python
from django.test import TestCase

from todos.models import Todo
from ..serializers import TodoSerializer


class TodoSerializerTest(TestCase):
    def test_input_valid(self):
        input_data = {"title": "test_input_valid"}
        serializer = TodoSerializer(data=input_data)

        self.assertEqual(serializer.is_valid(), True)

    def test_input_invalid_if_title_is_blank(self):
        input_data = {"title": ""}
        serializer = TodoSerializer(data=input_data)

        self.assertEqual(serializer.is_valid(), False)
        self.assertCountEqual(serializer.errors.keys(), ["title"])
        self.assertCountEqual([x.code for x in serializer.errors["title"]], ["blank"])

    def test_output_data(self):
        todo = Todo.objects.create(
            title="test_output_data",
        )
        serializer = TodoSerializer(instance=todo)

        expected_data = {
            "id": str(todo.id),
            "title": todo.title,
            "checked": todo.checked,
        }
        self.assertDictEqual(serializer.data, expected_data)

```

とはいえ、先ほどのビューのテストで作成を検証していて、`title`がブランクの時にエラーがでる処理を検証しているので少し被っている気もします。
上記の例で言えばシリアライザのテストのみでいいと思いますが、そもそもviewsetsを利用している場合は開発段階で十分テストされていると思うので、省略してしまっても大丈夫だと思います。

## モデルのテスト
こちらは通常のDjangoでのテストと変わらないので割愛します。

## 参考
- [現場で使える Django REST Framework の教科書](https://amzn.to/3FgLziJ)
- [Django REST framework](https://www.django-rest-framework.org/)
- [Django ドキュメント](https://docs.djangoproject.com/ja/4.0/)