[Django]Serializerのテストを作成してみる

## はじめに
自分用の個人開発のメモ/備忘録として記録していきます。

## 環境
django5.0
↓ dockerでの開発環境作成はこちら

https://qiita.com/mkthrkw/private/588226d30690c707d2b8

↓ 認証ユーザーモデルはこちらをそのまま利用

https://qiita.com/mkthrkw/private/b2ea8fb8f33a68faab87


## やりたいこと
Serializerのテストを一通り書いてみる

## 前回

### Modelのテスト

https://qiita.com/mkthrkw/items/c3fcebcbc17fe761bdd1

### Viewのテスト

https://qiita.com/mkthrkw/items/acd54a56f5cf600ca97b


## テストについて下調べ
おもに下記のようなテストをやる模様。
|No|カテゴリ|内容|
|--|----|----|
|1|シリアライザインスタンスの作成|テスト対象のシリアライザインスタンスを作成|
|2|バリデーションテスト|シリアライザが適切なバリデーションルールを適用していることを確認|
|3|シリアライズプロセスのテスト|オブジェクトが正しいJSONデータにシリアライズされるかをテスト|
|4|デシリアライズプロセスのテスト|JSONデータが正しくモデルインスタンスにデシリアライズされるかをテスト|
|5|フィールドレベルのテスト|定義したフィールドが適切に処理されているかを確認|



## 準備

### とりあえずSerializerを準備

・ユーザー＞プロジェクト＞リスト＞チケット＞コメントとリレーションしているモデルを利用
・チケットはコメントをネストしたTicketDetailSerializerを別途準備
・プロジェクトは、リスト＞チケットまでをネストしたProjectDetailSerializerを準備

``` python:serializers.py
from rest_framework  import serializers
from .models import Project, List, Ticket, Comment

class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = '__all__'

class TicketSerializer(serializers.ModelSerializer):
    class Meta:
        model = Ticket
        fields = '__all__'

class ListSerializer(serializers.ModelSerializer):
    class Meta:
        model = List
        fields = '__all__'

class ProjectSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())
    class Meta:
        model = Project
        fields = '__all__'

class TicketDetailSerializer(serializers.ModelSerializer):
    comments = CommentSerializer(many=True)
    class Meta:
        model = Ticket
        fields = '__all__'

class ListDetailSerializer(serializers.ModelSerializer):
    tickets = TicketSerializer(many=True)
    class Meta:
        model = List
        fields = '__all__'

class ProjectDetailSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())
    lists = ListDetailSerializer(many=True)
    class Meta:
        model = Project
        fields = '__all__'
```


## test_serializers.pyを書いていく

### setUp
テストのセットアップ
メソッドを使用してテストケースの初期データを設定
各テスト実行前に自動的に呼び出され、テストに必要なデータを生成
ProjectSerializerがCurrentUserDefault()を使っているので、APIRequestFactoryにてリクエストを作成し、userを割り当て
シリアライザに context パラメータを渡すことで、シリアライザ内で request を使用出来るようにする
``` python: test_serializers.py
class ProjectSerializerTestCase(TestCase):
    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(email='testuser@test.com', password='12345')
        self.project_data = data={'name': 'Example Project', 'description': 'Example Description'}
        self.factory = APIRequestFactory()
        self.request = self.factory.get('/')  # メソッドは何でもよい、リクエストを作る必要がある
        self.request.user = self.user  # テストユーザーをリクエストに割り当てる
```

### test_serializer_with_empty_data
空のデータに対するバリデーション
このテストメソッドは、空のデータでシリアライザを初期化し、そのバリデーションが失敗することを検証
``` python: test_serializers.py
def test_serializer_with_empty_data(self):
    serializer = ProjectSerializer(data={}, context={'request': self.request})
    self.assertFalse(serializer.is_valid())
    self.assertIn('name', serializer.errors)        
    self.assertNotIn('user', serializer.errors)
```

### test_serializer_with_valid_data
有効なデータに対するバリデーション
シリアライザに渡されたデータが有効であり、is_valid() メソッドが True を返すことを検証
``` python: test_serializers.py
def test_serializer_with_valid_data(self):
    serializer = ProjectSerializer(data=self.project_data, context={'request': self.request})
    self.assertTrue(serializer.is_valid())
```


### test_serializer_outputs_expected_data
シリアライズプロセスの検証
モデルインスタンスが正確にJSON形式にシリアライズされることを確認
``` python: test_serializers.py
def test_serializer_outputs_expected_data(self):
    model_instance = Project.objects.create(name='Example Project', user=self.user)
    serializer = ProjectSerializer(model_instance, context={'request': self.request})
    self.assertEqual(serializer.data['name'], 'Example Project')
    self.assertNotIn('user', serializer.data)
```

### test_deserialization_creates_proper_instance
デシリアライズとインスタンスの作成
提供されたデータからモデルインスタンスが正しく作成されることを確認
``` python: test_serializers.py
    def test_deserialization_creates_proper_instance(self):
        serializer = ProjectSerializer(data=self.project_data,  context={'request': self.request})
        self.assertTrue(serializer.is_valid())
        instance = serializer.save()
        self.assertEqual(instance.name, 'Example Project')
        self.assertEqual(instance.user, self.user)
```

＊CommentSerializer/TicketSerializer/ListSerializerも同様に作成する


### TicketDetailSerializer（JSON形式へのシリアライズのみ）
``` python: test_serializers.py
class TicketDetailSerializerTestCase(TestCase):
    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(email='testuser@test.com', password='12345')
        self.project = Project.objects.create(name='Example Project', user=self.user)
        self.list = List.objects.create(title='Example List', project=self.project)
        self.ticket = Ticket.objects.create(title='Example Ticket', list=self.list)
        self.comment = Comment.objects.create(text='Example Comment', ticket=self.ticket)
    
    def test_serializer_outputs_expected_data(self):
        serializer = TicketDetailSerializer(self.ticket)
        self.assertEqual(serializer.data['title'], 'Example Ticket')
        self.assertEqual(serializer.data['list'], self.list.id)
        self.assertEqual(serializer.data['comments'][0]['text'], 'Example Comment')
        self.assertEqual(serializer.data['comments'][0]['ticket'], self.ticket.id)
```

### ListDetailSerializer（JSON形式へのシリアライズのみ）
``` python: test_serializers.py
class ListDetailSerializerTestCase(TestCase):
    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(email='testuser@test.com', password='12345')
        self.project = Project.objects.create(name='Example Project', user=self.user)
        self.list = List.objects.create(title='Example List', project=self.project)
        self.ticket = Ticket.objects.create(title='Example Ticket', list=self.list)
    
    def test_serializer_outputs_expected_data(self):
        serializer = ListDetailSerializer(self.list)
        self.assertEqual(serializer.data['title'], 'Example List')
        self.assertEqual(serializer.data['project'], self.project.id)
        self.assertEqual(serializer.data['tickets'][0]['title'], 'Example Ticket')
        self.assertEqual(serializer.data['tickets'][0]['list'], self.list.id)
```

### ProjectDetailSerializer（JSON形式へのシリアライズのみ）
``` python: test_serializers.py
class ProjectDetailSerializerTestCase(TestCase):
    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(email='testuser@test.com', password='12345')
        self.project = Project.objects.create(name='Example Project', user=self.user)
        self.list = List.objects.create(title='Example List', project=self.project)
        self.ticket = Ticket.objects.create(title='Example Ticket', list=self.list)
    
    def test_serializer_outputs_expected_data(self):
        serializer = ProjectDetailSerializer(self.project)
        self.assertEqual(serializer.data['name'], 'Example Project')
        self.assertNotIn('user', serializer.data)
        self.assertEqual(serializer.data['lists'][0]['title'], 'Example List')
        self.assertEqual(serializer.data['lists'][0]['project'], self.project.id)
        self.assertEqual(serializer.data['lists'][0]['tickets'][0]['title'], 'Example Ticket')
        self.assertEqual(serializer.data['lists'][0]['tickets'][0]['list'], self.list.id)
```


## test実行
テスト実行
``` shell
python manage.py test
```
全てのテストが成功すればこのような表示となる
``` shell
Found 19 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...................
----------------------------------------------------------------------
Ran 19 tests in 4.555s

OK
Destroying test database for alias 'default'...
```


## おわりに
・serializerが複雑になれば、もっとテストも細かく作成する必要がありそう


## 参考

https://www.django-rest-framework.org/api-guide/serializers/