Guide

factory_boy holds a wide array of features, which can be combined into complex descriptions and definitions. This section will build a set of factories increasingly powerful, built onto those features.

We’ll run our examples around an imagined library management system: books, readers, etc.

Step 1: factories for a single model

Let’s start with authors; our data model could be the following:

class Author(Model):
    fullname = TextField()

    birthdate = DateField()
    death = DateField(null=True)

    main_language = CharField(max_length=2)  # iso639-1 alpha-2 language code

    def __str__(self):
        return "{name} ({birth} - {death}) [{lang}]".format(
            name=self.fullname,
            birth=self.birthdate.isoformat(),
            death=self.death.isoformat() if self.death else '',
            lang=self.main_language,
        )

A first factory

In order to have realistic random data, we’ll start with the following factory:

class BasicAuthorFactory(factory.Factory):
    fullname = factory.Faker('name')
    birthdate = factory.fuzzy.FuzzyDate(
        start_date=datetime.date(1, 1, 1),
        end_date=datetime.date.today() - datetime.timedelta(days=20 * 365),
    )
    death = None
    main_language = 'en'

    class Meta:
        model = Author

Let’s walk through the definitions:

  • death = None: each author will be considered alive.
  • main_language = 'en': Use the 'en' language code for each other; simpler to begin with.
  • fullname = factory.Faker(‘name’) will use a randomly yet human-looking name for every author
  • birthdate = factory.FuzzyDate(…): For every author, use a random date between 1 AD and 20 years ago (we’ll assume that most authors are older than 20 years old; and Python’s built-in date type won’t handle date before 1 AD).

If we create a few objects with this:

>>> BasicAuthorFactory()
Vincent Foster (1000-10-12 - ) [en]

>>> BasicAuthorFactory()
Christian Cole (1751-09-14 - ) [en]

# We want more!
>>> BasicAuthorFactory.create_batch(10)
[
 <Author: Sabrina Hicks (0996-05-21 - ) [en]>,
 <Author: Jennifer Guzman (0004-09-05 - ) [en]>,
 <Author: Renee Wiley (1436-07-26 - ) [en]>,
 <Author: Paul Morton (0777-12-08 - ) [en]>,
 <Author: Brianna Williams (0458-03-30 - ) [en]>,
 <Author: Carla Smith (1322-03-04 - ) [en]>,
 <Author: Darrell House (0940-12-27 - ) [en]>,
 <Author: Cody Collier (0762-09-08 - ) [en]>,
 <Author: James Bishop MD (0329-12-24 - ) [en]>,
 <Author: George Moore (0551-03-29 - ) [en]>,
]

This looks good! However, there are a few issues:

  • Some authors are rather old
  • Everyone has the same languege

Improving the AuthorFactory (lazy_attribute)

Let’s start with preventing immortality: we’ll decide that no author should live more than 100 years.

class MortalAuthorFactory(BasicAuthorFactory):
    @factory.lazy_attribute
    def death(self):
        cutoff = self.birthdate + datetime.timedelta(days=100 * 365)
        if cutoff < datetime.date.today():
            return cutoff
        else:
            # Too young to die
            return None

Here, we use a factory.lazy_attribute()-decorated function to compute our custom death date.

Note

Note how we inherit from the BasicAuthorFactory class for increased readability; this is a simple yet powerful technique when designing factories.

Let’s see this in action:

>>> MortalAuthorFactory()
<Author: Daniel Kelley (1724-02-17 - 1824-01-24) [en]>
>>> MortalAuthorFactory()
<Author: Laura Howard (0098-01-18 - 0197-12-25) [en]>

>>> MortalAuthorFactory()
<Author: William Nelson (1964-11-07 - ) [en]>

Better! However, we’ll quickly notice that all our authors die around age 100; this is quite unrealistic…

We could alter our death() function to use a random age; but, for the sake of this guide, we’ll imagine a more complex scenario.

Let’s say that our fictional library has a special “They Died too Young” section for great authors dead before their 30th birthday.

Using class Params for easier tuning

We’d like to be able to write the following test:

young = AuthorFactory(death_age=24)
old = AuthorFactory(death_age=40)
self.assertEqual([young], died_too_young_authors())

Let’s get to work:

class UnluckyAuthorFactory(MortalAuthorFactory):
    class Params:
        death_age = factory.fuzzy.FuzzyInteger(20, 100)

    @factory.lazy_attribute
    def death(self):
        cutoff = self.birthdate + datetime.timedelta(days=self.death_age * 366)
        if cutoff < datetime.date.today():
            return cutoff
        else:
            # Too young to die
            return None

Note the class Params section: this section of the factory can hold any valid declarations; they will be available to each other declarations as if they were part of the main class body.

However, they will be removed from the kwargs passed to the instance creation function.

Our database has more variety:

>>> UnluckyAuthorFactory()
<Author: Hailey Lee (1386-03-15 - 1442-04-27) [en]>
>>> UnluckyAuthorFactory()
<Author: Linda Bullock (1986-01-11 - ) [en]>,

We can even force an author’s death age:

>>> UnluckyAuthorFactory(death_age=42)
<Author: Amy Roberts (1003-08-02 - 1045-09-02) [en]>

Note

Within our death() function, we can read self.death_age even if this field will never be defined on the final object; within most factory-decorated functions, self refers to a fake stub (neither an instance of the factory class nor from the target model), where all fields from the factory declaration can be freely accesses.

Step 2: Handling connected models

We now have the tools to build simple objects; but most projects will require more complex, inter-connected models.

For instance, a library without books would be quite useless; let’s fill it.

Linking models: SubFactory

Our model is rather simple: a title and summary, an author, publication date, and language:

class Book(Model):
    title = TextField()
    summary = TextField()

    author = ForeignKey(Author)
    publication_date = DateField()
    language = CharField(max_length=2)

    def __str__(self):
        return """"{title}" by {author} (pub. {publication})""".format(
            title=self.title,
            author=self.author.fullname,
            publication=self.publication_date.isoformat(),
        )

For the title and summary, Faker provides great helpers.

Handling Author is more complex: we need to provide a proper object. We’ll reuse our UnluckyAuthorFactory with a SubFactory:

class BasicBookFactory(factory.Factory):
    title = factory.Faker('catch_phrase')
    summary = factory.Faker('text', max_nb_chars=2000)

    author = factory.SubFactory(UnluckyAuthorFactory)

    publication_date = factory.fuzzy.FuzzyDate(
        start_date=datetime.date(1, 1, 1),
    )
    language = 'en'

    class Meta:
        model = Book

Now, whenever we create a Book with a BasicBookFactory, factory_boy will first use the UnluckyAuthorFactory to create an author; and pass it as author= to our Book constructor:

>>> BasicBookFactory()
<Book: "Versatile reciprocal core" by Brett Dean (pub. 1983-04-29)>
>>> BasicBookFactory()
<Book: "Secured methodical superstructure" by Nancy Bryan (pub. 1843-02-18)>

>>> _.author
<Author: Nancy Bryan (1272-04-03 - 1340-05-25) [en]>

Improving inter-model consistency

Those books have a slight issue: most publication dates fall outside the author’s life - so many fakes!

Let’s make sure they were written when the author was alive, and at least 15. For this, we’ll need to force the publication date to happen between “birthdate + 15 years” and “deathdate or today”:

class AuthenticBookFactory(BasicBookFactory):
    class Params:
        min_publication_date = factory.LazyAttribute(
            lambda book: book.author.birthdate + datetime.timedelta(days=15 * 365),
        )
        max_publication_date = factory.LazyAttribute(
            lambda book: book.author.death or datetime.today(),
        )

    publication_date = factory.LazyResolver(
        factory.fuzzy.FuzzyDate,
        start_date=factory.SelfAttribute('..min_publication_date'),
        end_date=factory.SelfAttribute('..max_publication_date'),
    )

The two parameters min_publication_date and max_publication_date make our intent clearer, and allow users of this factory to choose more precisely their target publication date.

The actual publication_date is computed from those two fields, through a LazyResolver: this declaration can be seen as:

# Note: this is pseudo-code for the actual factory resolution algorithm.

# First, resolve min_publication_date / max_publication_date:
min_date = min_publication_date.evaluate(**context)
max_date = max_publication_date.evaluate(**context)

# Then, use them to prepare and compute the publication_date declaration:
pub_date_declaration = factory.fuzzy.FuzzyDate(start_date=min_date, end_date=max_date)
pub_date = pub_date_declaration.evaluate(**context)

# Finally, product the actual object
Book.objects.create(publication_date=pub_date)

Note

  • SelfAttribute will simply copy the value of another field within the factory, following a dotted path (use multiple dots to read fields from ancestors in a SubFactory chain)
  • Within a LazyResolver or a SubFactory, a SelfAttribute will be anchored to the inside of that declaration; go “up” a level to read fields from the containing factory.

We now have books written when the author was alive, and not too young:

>>> AuthenticBookFactory()
<Book: "Business-focused even-keeled productivity" by Lauren Ball (pub. 1201-07-30)>
>>> _.author
<Author: Lauren Ball (1129-12-20 - 1227-03-03) [en]>

If we assemble the features of both models, all data is kept consistent; for instance, forcing the death age at 18 will generate a book written when the author was aged 15 to 18.

>>> AuthenticBookFactory(author__death_age=18)
<Book: "Synergistic multi-tasking hierarchy" by Scott Elliott (pub. 1074-08-25)>
>>> _.author
<Author: Scott Elliott (1056-09-12 - 1074-09-26) [en]>