FactoryBoy / factory_boy

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

create_batch should create unique records #762

Open eugene-kulak opened 4 years ago

eugene-kulak commented 4 years ago

The problem

Let's say we have following factory for currency:

class CurrencyFactory(factory.django.DjangoModelFactory):
    """ Currency Factory
    """
    name = factory.Faker('currency_name')
    code = factory.Faker('currency_code')
    pegged = True

    class Meta:
        model = Currency
        django_get_or_create = ('code',)

then in the test we create few instances:

records = CurrencyFactory.create_batch(10)
print(records)
[<Currency: LRD>,
 <Currency: AMD>,
 <Currency: XOF>,
 <Currency: IQD>,
 <Currency: CHF>,
 <Currency: XOF>,
 <Currency: HKD>,
 <Currency: NAD>,
 <Currency: BOB>,
 <Currency: GBP>]
In [6]: list(set(records)) == list(records)
Out[6]: False

Proposed solution

create_batch should count real records and not just create calls

Extra notes

The real cause of this I think is the use of get_or_create, because it is bad practice in unittesting in general, but unfortunately, I don't see how this can be solved with sequences in factory boy (see problems with resetting all sequences between tests)

rbarrois commented 4 years ago

Hi!

Going this way would require a way to notify the factory that we only want "new instances", never reusing existing ones.

In your example, the issue is twofold:

  1. You're picking values from a finite set — Faker's currency provider contains only 164 options;
  2. Using django_get_or_create means you want to reuse values from the database.

As long as you're combining this two, what you want is impossible: if a previous step has already created 160 entries in the table, and you request a batch of "5 new records", the system would fail - there are only 4 available records left.