概要
Django+factory-boyでテストデータを作成する際に、結合先のテーブルに主キーが同一のレコードを複数INSERTしようとして「Duplicate entry」が発生してしまう事象を回避する方法について書く。(詳細は後述)
前提
Django: 3.0.7
factory-boy: 2.12.0
結論
以下のように主キーの重複を回避したいモデルのFactoryクラスのMetaクラスにdjango_get_or_create = ("${主キー名}",)
を定義する。
class PrefectureFactory(factory.django.DjangoModelFactory): class Meta: model = Prefecture django_get_or_create = ("prefecture_id",) prefecture_id = 13 prefecture_name = "東京都"
詳細
以下のようなアンケートの回答結果を格納するテーブル(survey_response)を例とする。
アンケートの回答項目は「氏名(name)」と「住んでる県(prefecture_id)」で、prefecture_idで「都道府県マスター(prefecture)」に外部キー参照する。
[models.py]
from django.db import models class Prefecture(models.Model): class Meta: db_table = "prefecture" prefecture_id = models.IntegerField(primary_key=True) prefecture_name = models.CharField(max_length=8, null=False) class SurveyResponse(models.Model): class Meta: db_table = "survey_response" response_id = models.IntegerField(primary_key=True) prefecture = models.ForeignKey( to="prefecture", db_column="prefecture_id", to_field="prefecture_id", on_delete=models.PROTECT, null=True, related_name="survey_response", )
ユニットテストを実装するために、以下のようにfactories.pyとtests.pyを記述する。
[factories.py]
import factory from survey.models import Prefecture, SurveyResponse class PrefectureFactory(factory.django.DjangoModelFactory): class Meta: model = Prefecture prefecture_id = 13 prefecture_name = "東京都" class SurveyResponseFactory(factory.django.DjangoModelFactory): class Meta: model = SurveyResponse response_id = 1 prefecture = factory.SubFactory(PrefectureFactory)
[tests.py]
from django.test import TestCase from survey.factories import SurveyResponseFactory class TestModel(TestCase): @classmethod def setUpTestData(cls): SurveyResponseFactory.create(response_id=1) SurveyResponseFactory.create(response_id=2) def test_1(self): self.assertEqual(1, 1)
・この状態で
$ python manage.py test
を実行すると「Duplicate entry」が発生してしまう。
以下は、SQLiteの場合のエラーメッセージ
django.db.utils.IntegrityError: UNIQUE constraint failed: prefecture.prefecture_id
factory-boyは存在確認せずに「prefecture」テーブルにprefecture_id=13のレコードをINSERTしようとしてしまうことが原因である。
解決までの経緯
まずは以下のstackoverflowの記事を見つけた。
stackoverflow.com
この回答の通りに以下のように設定してみた。
class PrefectureFactory(factory.django.DjangoModelFactory): class Meta: model = Prefecture FACTORY_DJANGO_GET_OR_CREATE = ("prefecture_id",) prefecture_id = 13 prefecture_name = "東京都"
testを実行すると、以下のエラーメッセージが発生した。
TypeError: Prefecture() got an unexpected keyword argument 'FACTORY_DJANGO_GET_OR_CREATE'
公式ドキュメントを確認したところ、「FACTORY_*」というクラス変数に設定値を定義するのは古いスタイルで、新しいスタイルでは設定値はMetaクラスに定義するとのことである。
factoryboy.readthedocs.io
Metaクラスに同等の設定値を定義する方法を調べたところ、以下のstackoverflowの記事を見つけた。
stackoverflow.com
上記の回答に最新の公式ドキュメントのリンクも貼ってくださっていた。
factoryboy.readthedocs.io
ドキュメントの通りに以下のように設定すると、「Duplicate entry」が発生しなくなった。
class PrefectureFactory(factory.django.DjangoModelFactory): class Meta: model = Prefecture django_get_or_create = ("prefecture_id",) prefecture_id = 13 prefecture_name = "東京都"