Why does FactoryBoy create a new object from SubFactory despite FACTORY_DJANGO_GET_OR_CREATE
Asked Answered
M

3

6

I have just started using factory boy with Django. It has a setting FACTORY_DJANGO_GET_OR_CREATE that means it won't create a new object if one already exists. But when I ask for an existing object with an existing SubFactory object, it creates an unused object despite this setting.

For example, in a brand new project, I tried:

# models.py
from django.db import models

class A(models.Model):
    name = models.CharField(max_length=10)

class B(models.Model):
    name = models.CharField(max_length=10)
    a = models.ForeignKey(A)

And

# factories.py
import factory

from . import models

class AFactory(factory.DjangoModelFactory):
    FACTORY_FOR = models.A
    FACTORY_DJANGO_GET_OR_CREATE = ('name',)

    name = factory.Sequence(lambda n: 'A-{0}'.format(n))

class BFactory(factory.DjangoModelFactory):
    FACTORY_FOR = models.B
    FACTORY_DJANGO_GET_OR_CREATE = ('name',)

    name = factory.Sequence(lambda n: 'B-{0}'.format(n))
    a = factory.SubFactory(AFactory)

Now:

from factories import *

a = AFactory(name="Apple")
models.A.objects.all()
# one object
b = BFactory(a__name="Apple", name="Beetle")
models.B.objects.all()
models.A.objects.all()
# one A object, one B object
b = BFactory(name="Beetle")
models.B.objects.all()
models.A.objects.all()
# still one B object, but now a new, unused A object too

Then the final call to BFactory has brought into being a new object of class A, even though the B object with name Beetle already exists (and is not re-created). Why, and how do I stop this new A object being created?

(I know I can get around this by calling instead:

b = BFactory(name="Beetle", a__name="Apple")

but in my actual use case, I have several dependencies and levels of hierarchy, and it's messy to supply extra redundant parameters this way - and I can't seem to get the right combination of parameters.)

Thanks!

Misti answered 20/6, 2013 at 6:28 Comment(0)
M
1

The above is a problem when I also have a class C that has a foreign key to B. When I created a new CFactory object like so:

c = CFactory(name="Cat", b__name="Beetle")

it also creates a new unused A object.

I have found the solution to this is simple: instead, make the new CFactory object like this:

c = CFactory(name="Cat", b=b)

where b is either a B object or a BFactory object.

Misti answered 5/7, 2013 at 21:40 Comment(0)
S
1

You could call use:

a = SubFactory(AFactory, name='Reuse this model A instance')

That way the following two invocations are equal:

BFactory()
BFactory(a__name='Reuse this model A instance')
Shawanda answered 6/6, 2014 at 13:23 Comment(0)
S
0

I believe the subfactory kicks in after the get_or_create irrespectively.

Not sure of a s9lution though, it's not a use case I've come across. IMHO fixtures that depend on get_or_create have too much potential for magic. The alternative (always create) can be a little more tedious if you have a complicated test case, but is safer

Syndicalism answered 20/6, 2013 at 12:4 Comment(2)
Thanks! Say A is a user and B is a post. With the code above, if I create a new post, it always creates a new user and associates the post with that new user. Can I use factory boy to test one user with several posts?Misti
I realised my comment is unclear - one user with several posts is easy if you set FACTORY_DJANGO_GET_OR_CREATE as I do above. But I actually have users owning businesses, and the businesses make the posts (i.e. there's a third class C). In fact there can be other objects with ForeignKeys to the posts too (classes D, E...). So every time I create one of these downstream objects, I get a whole hierarchy of newly created (and unused) objects. That's what I'd like to avoid.Misti

© 2022 - 2024 — McMap. All rights reserved.