FactoryBoy / Django-OneToOneField重复键错误

时间:2019-04-12 08:48:23

标签: django factory-boy

我正在为具有多个应用程序的大型Django应用程序编写测试。作为此过程的一部分,我将逐步为Django项目中不同应用程序的所有模型创建工厂。

但是,我遇到了一些与FactoryBoy混淆的行为

我们的应用使用Profiles,该链接通过auth.models.User链接到默认的OneToOneField模型

class Profile(models.Model):
    user = models.OneToOneField(User)
    birth_date = models.DateField(
        verbose_name=_("Date of Birth"), null=True, blank=True)
    ( ... )

我为这两种模型创建了以下工厂:

@factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):

    class Meta:
        model = profile_models.Profile

    user = factory.SubFactory('yuza.factories.UserFactory')
    birth_date = factory.Faker('date_of_birth')
    street = factory.Faker('street_name')
    house_number = factory.Faker('building_number')
    city = factory.Faker('city')
    country = factory.Faker('country')
    avatar_file = factory.django.ImageField(color='blue')
    tenant = factory.SubFactory(TenantFactory)


@factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):

    class Meta:
        model = auth_models.User

    username = factory.Faker('user_name')
    first_name = factory.Faker('first_name')
    last_name = factory.Faker('last_name')

    email = factory.Faker('email')
    is_staff = False
    is_superuser = False
    is_active = True
    last_login = factory.LazyFunction(timezone.now)

    profile = factory.RelatedFactory(ProfileFactory, 'user')

然后我针对以下哪个运行以下测试:

class TestUser(TestCase):

    def test_init(self):
        """ Verify that the factory is able to initialize """
        user = UserFactory()
        self.assertTrue(user)
        self.assertTrue(user.profile)
        self.assertTrue(user.profile.tenant)


class TestProfile(TestCase):

    def test_init(self):
        """ Verify that the factory is able to initialize """
        profile = ProfileFactory()
        self.assertTrue(profile)

TestUser中的所有测试均通过,但是TestProfile在工厂初始化(profile = ProfileFactory())上失败,并引发以下错误:

IntegrityError: duplicate key value violates unique constraint "yuza_profile_user_id_key"
DETAIL:  Key (user_id)=(1) already exists.

我不清楚为什么会有一个重复的用户((应该只有一个调用来创建一项权限吗?特别是因为已禁用任何干扰信号)

我的代码基于FactoryBoy文档中的the example,该文档还处理了通过OneToOneKey连接的“用户” /“个人资料”

有人知道我在做什么错吗?

更新

根据Bruno和ivissani的建议,我已将user中的ProfileFactory行更改为

user = factory.SubFactory('yuza.factories.UserFactory', profile=None)

现在,上述所有测试均成功通过!

但是,我仍然遇到以下问题-当其他工厂将UserFactory称为

IntegrityError: duplicate key value violates unique constraint "yuza_profile_user_id_key"
DETAIL:  Key (user_id)=(1) already exists.

仍然返回。

我在下面提供了一个名为UserFactory的工厂的示例,但是每个具有user字段的工厂都会遇到这种情况。

class InvoiceFactory(factory.django.DjangoModelFactory):

    class Meta:
        model = Invoice

    user = factory.SubFactory(UserFactory)
    invoice_id = None
    title = factory.Faker('catch_phrase')
    price_paid = factory.LazyFunction(lambda: Decimal(0))
    tax_rate = factory.LazyFunction(lambda: Decimal(1.21))
    invoice_datetime = factory.LazyFunction(timezone.now)

user上的InvoiceFactory字段更改为

user = factory.SubFactory(UserFactory, profile=None)

帮助它通过了一些测试,但由于不再具有与之关联的配置文件而最终陷入麻烦。

奇怪的是以下内容(在出厂前声明了用户)可以正常工作:

self.user = UserFactory()
invoice_factory = InvoiceFactory(user=self.user)

我不清楚为什么我仍然在这里遇到IntegrityError,现在调用UserFactory()可以正常工作。

1 个答案:

答案 0 :(得分:2)

我认为这是因为您的ProfileFactory使用User创建了一个UserFactory实例,而该实例本身试图使用Profile创建了一个新的ProfileFactory实例。

您需要按照in the documentation you link to中所述打破这一周期:

# We pass in profile=None to prevent UserFactory from 
# creating another profile (this disables the RelatedFactory)
user = factory.SubFactory('yuza.factories.UserFactory', profile=None)

如果这对您不起作用,并且您需要更高级的处理,那么我建议您实施post_generation hook,以便可以执行更高级的操作。

编辑:

另一种选择是告诉工厂男孩不要使用django_get_or_create optionProfile重新创建User

@factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):

    class Meta:
        model = profile_models.Profile
        django_get_or_create = ('user',)

如果这样做,则可以删除我之前建议的profile=None

编辑2:

这可能也有帮助,使用UserFactory.profile钩子来更改post_generation

@factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):

    class Meta:
        model = auth_models.User

    ...

    # Change profile to a post_generation hook
    @factory.post_generation
    def profile(self, create, extracted):
         if not create:
             return
         if extracted is None:
             ProfileFactory(user=self)

编辑3

我刚刚意识到您username中的UserFactory字段与事实上男孩的文档中的字段不同,在Django中为unique。我想知道这是否不会导致某些旧实例被重用,因为用户名相同。

您可能想尝试将此字段更改为工厂中的序列:

@factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):

    class Meta:
        model = auth_models.User

    # Change to sequence to avoid duplicates
    username = factory.Sequence(lambda n: "user_%d" % n)