I am writing tests for a large Django application, as part of this process I am gradually creating factories for all models of the different apps within the Django project.
However, I've run into some confusing behavior with FactoryBoy where it almost seems like
SubFactories have an max depth beyond which no instances are generated.
The error occurs when I try to run the following test:
def test_subfactories(self): """ Verify that the factory is able to initialize """ user = UserFactory() self.assertTrue(user) self.assertTrue(user.profile) self.assertTrue(user.profile.tenant) order = OrderFactory() self.assertTrue(order) self.assertTrue(order.user.profile.tenant)
The last line will fail (
AssertionError: None is not true), running this test through a debugger reveals that indeed order.user.profile.tenant returns
None instead of the expected
There are quite a few factories / models involved here, but the layout is relatively simple.
User (django default) and the
Profile model are linked through a OneToOneField, which (after some trouble) is represented by the
@factory.django.mute_signals(post_save) class ProfileFactory(factory.django.DjangoModelFactory): class Meta: model = yuza_models.Profile django_get_or_create = ('user',) 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.Sequence(lambda n: "user_%d" % n) 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) @factory.post_generation def profile(self, create, extracted): if not create: return if extracted is None: ProfileFactory(user=self)
TenantFactory below is represented as a
SubFactory on the
class TenantFactory(factory.django.DjangoModelFactory): class Meta: model = elearning_models.Tenant name = factory.Faker('company') slug = factory.LazyAttribute(lambda obj: text.slugify(obj.name)) name_manager = factory.Faker('name') title_manager = factory.Faker('job') street = factory.Faker('street_name') house_number = factory.Faker('building_number') house_number_addition = factory.Faker('secondary_address')
Order is linked to a
User, but many of its methods call fields of its
class OrderFactory(factory.DjangoModelFactory): class Meta: model = Order user = factory.SubFactory(UserFactory) order_date = factory.LazyFunction(timezone.now) price = factory.LazyFunction(lambda: Decimal(random.uniform(1, 100))) site_tenant = factory.SubFactory(TenantFactory) no_tax = fuzzy.FuzzyChoice([True, False])
Again, most of the asserts in the test pass without failing, all separate factories are able to initialize fetch values from their immediate foreignkey relations. However, as soon as factories/models are three steps removed from each other the call will return None instead of the expected
Since I was unable to find any reference to this behaviour in the FactoryBoy documentation its probably a bug on my side, but so far I've been unable to determine its origin. Does anyone know what I am doing wrong?
def create_user_profile(sender, instance, created, **kwargs): if created: profile = Profile.objects.create(user=instance) resume = profile.get_resume() resume.initialize() post_save.connect(create_user_profile, sender=User)