Factory Boyでユニーク値をもつモデルに逆参照したら重複した話
テストを記述しているときに参照先のユニークIDが重複して生成されるというエラーが起きてハマった。 モデルは以下
class User(models.Model): … some_id = models.CharField(… unique=True) … class Post(models.Model): some_id = models.ForeignKey(User, related_name=“posts”, to_field=“some_id”, on_delete=models.CASCADE) … class PostFactory(DjangoModelFactory): class Meta: model = Post some_id = SubFactory(SubFactory) class UserFactory(DjangoModelFactory): class Meta: model = User some_id = str(uuid.uuid4()) …
最初は上記のようなコードで動くかと思いshellを動かしてみたら2回目のビルドに落ちることに気づいた。エラー内容としては、userモデルのsome_idがuniqueなのに重複しているというエラーだ。
原因としてはテスト(一度のプログラムの実行)では、異なるUserインスタンスを生成してくれているわけでは無く、全く同じ内容のインスタンスが生成されているらしい。
つまり、一度目は、当然新規に発行されたUserなので重複することはないが、二度目は一度目と同じデータになるので、Unique制限に引っかかっていたということだ。
このエラーはFactoryBoyあるあるらしく、公式を見て回っていたら普通に回避方法が見つかった。
class UserFactory(DjangoModelFactory): class Meta: model = User fields = “__all__” django_get_or_create = (“some_id”,)
django_get_or_createに重複させたくないカラムを指定することで、作成かgetで適切に動作を処理してくれるようになる。
この場合、一度目の実行では新たにUserを作成し、二度目は作成しない。
後日談: Userモデルをページネーション以下で利用しようとたらget_or_createのおかげでUserを必要数作成することができなかった。 その為、FactoryboyのSequenceを使って、都度別々のuuidを作るように対応したけど、使い道によってファクトリー分けてもよさそう。
class UserFactory(DjangoModelFactory): class Meta: model = User uid = Sequence(lambda n: f"{str(uuid.uuid4())}-{n}")