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 Tenant
instance.
There are quite a few factories / models involved here, but the layout is relatively simple.
The User
(django default) and the Profile
model are linked through a OneToOneField, which (after some trouble) is represented by the UserFactory
and ProfileFactory
@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)
The TenantFactory
below is represented as a SubFactory
on the ProfileFactory
above.
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')
The Order
is linked to a User
, but many of its methods call fields of its self.user.profile.tenant
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 Tenant
instance.
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?
post_save method
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)
order.refresh_from_db()
before the assertions. Also are there any customsave
methods in those models? PostGeneration (yourUser.profile
) includes a call toinstance.save()
before finishing. Do the tables forProfile
andTenant
hold anything/what you would expect? – CassellaUserFactory.profile
(yes I am aware that the fault appears to be withProfileFactor.tenant
). Does the test pass if you use the earlier created user instance:order = OrderFactory(user = user)
? – Cassella.refresh_from_db()
before the assert did not pass - order = OrderFactory(user = user) does pass! - Order has a custom save method to set some datefields on save, but disabling this method did not change anything - Your comments about UserProfile did lead me to further examine theProfile
object, there exists a post_save method (included in my post above) that created aProfile
onUser
creation. I accounted for this signal by using the@factory.django.mute_signals
decorater on both theUserFactory
and theProfileFactory
– SouthwestwardOrderFactory
as well the tests passed! I had assumed that any calls onOrder.user
would trigger theUserFactory
which had already been enclosed with the decorator - but evidently this wasnt enough. – Southwestward