简体   繁体   中英

Django Tests - User Factory with create_batch

Ok I've faced interesting problem with my tests files.

I'm using this simple code to check User's created by UserFactory

UserFactory.create_batch(4)

for u in User.objects.all():
    print(u.email)

UserFactory - this works fine

Creates 4 users in create_batch(4) process

from django.contrib.auth import get_user_model
from factory import Faker
from factory.django import DjangoModelFactory

class UserFactory(DjangoModelFactory):
    email = Faker("email")
    password = Faker(
        "password",
        length=42,
        special_chars=True,
        digits=True,
        upper_case=True,
        lower_case=True,
    )

    class Meta:
        model = get_user_model()
        django_get_or_create = ["email"]

UserFactory - this one doesn't work

Creates only 1 user in create_batch(4) process

from django.contrib.auth import get_user_model
from faker import Faker
from factory.django import DjangoModelFactory

fake = Faker()

class UserFactory(DjangoModelFactory):

email = fake.email()
password = fake.password()

class Meta:
    model = get_user_model()
    django_get_or_create = ["email"]

The only difference is the way I generate user's email. factory.Faker("email) works fine but faker.email() doesn't.

Maybe someone had the same issue?


Your issue comes from the way Python works.

When you write the following:

class Foo(Bar):
  email = fake.email()
  name = fake.name()

What happens is, more or less:

  1. Python calls fake.email() , and receives a value (let's say jane@example.org )

  2. Python calls fake.name() , and receives a value (for instance "Johannes" )

  3. Python builds the dict of all class-level declarations for the target class — here {"email": "jane@example.org", "name": "Johannes"}

  4. This is passed to type to create the class:

     Foo = type( "Foo", # Name of the class being created [Bar], # List of its bases {"email": "jane@example.org", "name": "Johannes"}, # Methods & attributes )
  5. The class is now defined, and available for the rest of the code.

In other words, the lines are executed as they would be for the following code:

fake = faker.Faker()
fake_email = fake.email()
fake_name = fake.name()

class UserFactory(DjangoModelFactory):
  email = fake_email
  name = fake_name
  ...

As you can see, the UserFactory receives the results from the calls to faker, it has no way to know how those values were generated.

This is the reason for the various declarations ( factory.LazyAttribute , factory.Faker , etc): here, we pass factory-specific objects with a custom method which is called by FactoryBoy's builder code whenever a new instance needs building:

fake_email_maker = factory.Faker("email")
fake_name_maker = factory.Faker("name")

class UserFactory(DjangoModelFactory):
  email = fake_email_maker
  name = fake_name_maker
  ...

When you call UserFactory() , what happens looks like the following:

# UserFactory()
>>> fields = {}
>>> fields["name"] = UserFactory._meta.declarations...name.evaluate(...)
>>> fields["email"] = UserFactory._meta.declarations...evaluate(...)
>>> return User.objects.create(**fields)

The reason for getting the same password every time is that you're only getting the value of the call to fake.password() . That only gets called once, when the file is imported.

Use factory.LazyFunction(fake.name) intead of fake.name() and don't add () when you wrappe with factory.LazyFunction.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM