FactoryBoy / factory_boy

A test fixtures replacement for Python
https://factoryboy.readthedocs.io/
MIT License
3.49k stars 392 forks source link

Question: Django get existing object from db #892

Closed 1oglop1 closed 2 years ago

1oglop1 commented 2 years ago

Hi, I populate the database with a fixture that contains a known set of objects before tests.

I'd like to use factory boy to pick a specific item from the table.

According to the documentation https://factoryboy.readthedocs.io/en/stable/recipes.html#choosing-from-a-populated-table I can use the Iterator

import factory, factory.django
from . import models

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.User

    language = factory.Iterator(models.Language.objects.all())

However, I'd like to get a specific object by pk. My question is what is the most correct way to do this? Because if I do this:

import factory, factory.django
from . import models

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.User

    language = models.Language.objects.get(pk=2)

I get an error

RuntimeError: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.

A possible workaround is this:

import factory, factory.django
from . import models

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.User

    language = factory.Iterator(models.Language.objects.filter(pk=2))

I do not like the fact that it returns a queryset of 1 object. Is there a better way to do this? Thank you!

micuda commented 2 years ago

Hi @1oglop1, I think you can use factory.LazyFunction.

import factory, factory.django
from . import models

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.User

    language = factory.LazyFunction(lambda: models.Language.objects.get(pk = 2))
rbarrois commented 2 years ago

Exactly!

Although, if you want to be able to choose the PK when calling the factory (maybe using a "natural" key), you could do the following:

import factory, factory.django
from . import models

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.User

    class Params:
        language_code = "en-gb"

    language = factory.LazyAttribute(lambda o: models.Language.objects.get(code=o.language_code))
1oglop1 commented 2 years ago

I had a feeling that this should be achievable with Lazy* object. I just did not know how to use them properly! Thank you both for this example, it has helped me a lot!