Reputation: 1370
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?
Upvotes: 1
Views: 2348
Reputation: 11
Use factory.LazyFunction(fake.name)
intead of fake.name()
and don't add () when you wrappe with factory.LazyFunction.
Upvotes: 1
Reputation: 3599
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:
Python calls fake.email()
, and receives a value (let's say [email protected]
)
Python calls fake.name()
, and receives a value (for instance "Johannes"
)
Python builds the dict of all class-level declarations for the target class — here {"email": "[email protected]", "name": "Johannes"}
This is passed to type
to create the class:
Foo = type(
"Foo", # Name of the class being created
[Bar], # List of its bases
{"email": "[email protected]", "name": "Johannes"}, # Methods & attributes
)
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)
Upvotes: 3
Reputation: 7529
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.
Upvotes: 0