Pytest FactoryBoy

Factory Boy jest narzędziem tworzącym fabryki dla obiektów, co oznacza, że nie musimy ręcznie tworzyć potrzebnych obiektów do testów, ale możemy je wygenerować od razu w podanej ilości w bardzo prosty sposób. Możemy ustawiać na tworzonych obiektach własne wartości tylko dla zmiennych które chcemy przetestować.

pytest-factoryboy jest dodatkiem do pytest pozwalającym na wykorzystanie fabryk jako fixture. Pozwala również na wykorzystanie markera parametrize do parametryzowania tworzonych fabryk. Dzięki takiemu rozwiązaniu nie musimy importować do naszych testów fabryk a wykorzystując odpowiednią składnię możemy importować gotowe obiekty utworzone w bazie danych.

Instalacja

$ pip install pytest-factoryboy

Uwaga

Warto zainstalować najnowszą wersję 2.0.1. Obecnie tylko ta wersja zapewnia wsparcie dla najnowszej wersji factoryboy 2.10.0

Daklarowanie i tworzenie fabryk

Tworzenie fabryki polega na utworzeniu klasy która odzwierciedla pola klasy dla której tworzymy fabrykę. Ważne jest aby w klasie Meta w atrybucie model zdefiniować dla jakiego modelu budujemy fabrykę.

import factory
from . import models

class UserFactory(factory.Factory):
    first_name = 'John'
    last_name = 'Doe'
    admin = False

    class Meta:
        model = models.User

# Another, different, factory for the same object
class AdminFactory(factory.Factory):
    first_name = 'Admin'
    last_name = 'User'
    admin = True

    class Meta:
        model = 'user.User'

Nową fabryję możemy utworzyć na 3 sposoby:

# Zwrócenie instancji, która nie jest zapisana
user = UserFactory.build()

# Zwrócenie instancji, która została zapisana
user = UserFactory.create()

# Zwrócenie obiektu stub (tylko kilka atrybutów)
obj = UserFactory.stub()

Możemy również utworzyć kilka obiektów:

# Zwrócenie 10 instancji, które nie są zapisane
users = UserFactory.build_batch(10, first_name="Joe")

# Zwrócenie 10 instancji które zostały zapisane w bazie danych
users = UserFactory.create_batch(10, first_name="Joe")

# Zwrócenie 10 obiektu stub posiadających tylko kilka atrybutów
users = UserFactory.stub_batch(10, first_name="Joe")

Typy tworzonych pól

FactoryBoy zawiera duża liczbę typów pól dzięki którym możemy przygotować fabrykę która, w odpowiedni sposób będzie generować obiekty.

Faker

Aby łatwo zdefiniować realistycznie wyglądające fabryki, najczęściej wykorzystywany zostaje atrybutu Faker. Działanie tego atrybutu jest bardzo proste, jako pierwszy argument podajemy funkcję modułu Faker http://faker.readthedocs.io/en/master/providers.html

Przykładowo z modułu faker.providers.person wybieramy funkcję name. Jako dodatkowy argument możemy podać język w jakim ma zostać utworzony atrybut.

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

    username = factory.Faker('name', locale='pl_PL')

Z modułu faker.providers.lorem wybierając funckję paragraph możemy jako argument przekazać dodatkowe parametry.

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

    about_me = factory.Faker('paragraph', nb_sentences=3, variable_nb_sentences=True, locale='pl_PL')

Słownik

Jeśli nasze pole oczekuje słownika możemy je utworzyć w poniższy sposób. Chcąc odwołać się do atrybutów obiektu musimy wpisać ..is_superuser.

class UserFactory(factory.Factory):
    class Meta:
        model = User

    is_superuser = False
    roles = factory.Dict({
        'role1': True,
        'role2': False,
        'role3': factory.Iterator([True, False]),
        'admin': factory.SelfAttribute('..is_superuser'),
    })

Lista

Możemy również utworzyć listę. Wewnętrznie, pola są konwertowane na indeks=wartość, co umożliwia zastąpienie niektórych wartości w czasie ich użycia.

class UserFactory(factory.Factory):
    class Meta:
        model = User

    flags = factory.List([
        'user',
        'active',
        'admin',
    ])
>>> u = UserFactory(flags__2='superadmin')
>>> u.flags
['user', 'active', 'superadmin']

Sekwencje

Jeśli pole ma posiadać unikalny klucz, każdy obiekt generowany przez fabrykę powinien mieć inną wartość dla tego pola. Aby osiągnąć taki efekt wykorzystujemy deklarację sekwencji:

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

    username = factory.Sequence(lambda n: 'user%d' % n)

Jeśli jes ona bardziej skomplikowana można ją również zapisać w poniższy sposób.

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

    @factory.sequence
    def username(n):
        return 'user%d' % n

Każde wywołanie obiektu wygeneruje nam nowy niepowtarzalny atrybut.

>>> UserFactory()
<User: user0>
>>> UserFactory()
<User: user1>

Maybe

Czasami sposób budowania danego pola może zależeć od wartości innego, na przykład parametru. W takich przypadkach można użyj deklaracji Maybe: przyjmuje nazwę pola „decydującego” oraz dwie deklaracje. w zależności od wartości pola, którego nazwa jest przechowywana w parametrze „decydującym”, zastosuje efekty jednej lub drugiej deklaracji.

class UserFactory(factory.Factory):
    class Meta:
        model = User

    is_active = True
    deactivation_date = factory.Maybe(
        'is_active',
        yes_declaration=None,
        no_declaration=factory.fuzzy.FuzzyDateTime(timezone.now() - datetime.timedelta(days=10)),
    )
>>> u = UserFactory(is_active=True)
>>> u.deactivation_date
None
>>> u = UserFactory(is_active=False)
>>> u.deactivation_date
datetime.datetime(2017, 4, 1, 23, 21, 23, tzinfo=UTC)

LazyFunction

W prostych przypadkach wywołanie funkcji wystarcza aby utworzyć wartości dla pól. Jeśli ta funkcja nie zależy od budowanego obiektu, najlepiej użyć LazyFunction, aby wywołać tę funkcję. LazyFunction otrzymuje funkcję, która nie przyjmuje żadnych argumentów.

class LogFactory(factory.Factory):
    class Meta:
        model = models.Log

    timestamp = factory.LazyFunction(datetime.now)
>>> LogFactory()
<Log: log at 2016-02-12 17:02:34>

>>> # The LazyFunction can be overriden
>>> LogFactory(timestamp=now - timedelta(days=1))
<Log: log at 2016-02-11 17:02:34>

LazyAttribute

Gdy mamy sytuację w której nasze pole jest zależne od innych najlepiej wykorzystać LazyAttribute. Dobrym przykładem może być generowanie adresu e-mail w oparciu o nazwię użytkownika.

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

    username = factory.Sequence(lambda n: 'user%d' % n)
    email = factory.LazyAttribute(lambda obj: '%s@example.com' % obj.username)

Jeśli posiadamy bardziej rozbudowaną logikę możemy wykorzystać dekorator

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

    username = factory.Sequence(lambda n: 'user%d' % n)

    @factory.lazy_attribute
    def email(self):
        return '%s@example.com' % self.username
>>> UserFactory()
<User: user1 (user1@example.com)>

>>> # The LazyAttribute handles overridden fields
>>> UserFactory(username='john')
<User: john (john@example.com)>

>>> # They can be directly overridden as well
>>> UserFactory(email='doe@example.com')
<User: user3 (doe@example.com)>

FileField

Specialnie dla modelu Django został przygotowany atrybut factory.django.FileField. Pozwala on na utworzenie pliku dla generowanej fabryki.

class MyFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.MyModel

    the_file = factory.django.FileField(filename='the_file.dat')
>>> MyFactory(the_file__data=b'uhuh').the_file.read()
b'uhuh'
>>> MyFactory(the_file=None).the_file
None

ImageField

Istnieje również atrybut django.db.models.ImageField pozwalający na tworzenie obrazków.

class MyFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.MyModel

    the_image = factory.django.ImageField(color='blue')
>>> MyFactory(the_image__width=42).the_image.width
42
>>> MyFactory(the_image=None).the_image
None

Non-kwarg arguments

Niektóre klasy pobierają najpierw kilka non-kwarg argumentów. Taki typ pola można obsłużyć za pomocą atrybutu inline_args.

class MyFactory(factory.Factory):
    class Meta:
        model = MyClass
        inline_args = ('x', 'y')

    x = 1
    y = 2
    z = 3
>>> MyFactory(y=4)
<MyClass(1, 4, z=3)>

Parametry

Jeśli tworzone pole jest zależne od atrybutu nie będącego polem w rzeczywistym modelu tworzonym przez fabrykę należy wykorzystać deklarację Paramtru.

class RentalFactory(factory.Factory):
    class Meta:
        model = Rental

    begin = factory.fuzzy.FuzzyDate(start_date=datetime.date(2000, 1, 1))
    end = factory.LazyAttribute(lambda o: o.begin + o.duration)

    class Params:
        duration = 12
>>> RentalFactory(duration=0)
<Rental: 2012-03-03 -> 2012-03-03>
>>> RentalFactory(duration=10)
<Rental: 2008-12-16 -> 2012-12-26>

Cechy

Jeśli natomiast wiele pól ma zostać zaktualizowanych na podstawie flagi należy wykorzystać deklarację Cechy.

class OrderFactory(factory.Factory):
    status = 'pending'
    shipped_by = None
    shipped_on = None

    class Meta:
        model = Order

    class Params:
        shipped = factory.Trait(
            status='shipped',
            shipped_by=factory.SubFactory(EmployeeFactory),
            shipped_on=factory.LazyFunction(datetime.date.today),
        )
>>> OrderFactory()
<Order: pending>
>>> OrderFactory(shipped=True)
<Order: shipped by John Doe on 2016-04-02>

Fabryki w Django

Wszystkie fabryki modelu Django powinny używać klasy bazowej DjangoModelFactory. Jeśli zachodzi potrzeba utworzenia całkiem nie standardowej fabryki warto skorzystać z dokumentacji FactoryBoy https://factoryboy.readthedocs.io/en/latest/recipes.html

Deklarowanie fabryk

Deklaracja przebiega w dokładnie taki sam sposób jak tworzenie fabryki z prostej klasy. Dziedzicząc jednak z DjangoModelFactory otzymujemy do ustawień 2 dodatkowe paramtery. django_get_or_create oraz database. Pierwszy z nich pokreśla w jaki sposób mają zostać tworzone obiekty a drugi określa jakie bazy danych chcemu używać.

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = 'myapp.User'  # Equivalent to ``model = myapp.models.User``
        django_get_or_create = ('username',)

    username = 'john'
>>> UserFactory()                   # Creates a new user
<User: john>
>>> User.objects.all()
[<User: john>]

>>> UserFactory()                   # Fetches the existing user
<User: john>
>>> User.objects.all()              # No new user!
[<User: john>]

>>> UserFactory(username='jack')    # Creates another user
<User: jack>
>>> User.objects.all()
[<User: john>, <User: jack>]

Strategie tworzenia

Tworząc obiekt posiadamy tylko dwie podstawowe strategie określające w jaki sposób ma on zostać utworzony obiekt podczas wywołania fabryki. Pierwsza z nich build tworzy obiekt lokalnie, natomiast druga create tworzy lokalny obiekt i zapisuje go w bazie danych.

Domyślną strategią wywołania fabryki jest create, można jednak to zmienić ustawiając atrybut strategii Meta klasy.

Podstawowe strategie to factory.BUILD_STRATEGY oraz factory.CREATE_STRATEGY.

class ImageFactory(factory.Factory):
    # The model expects "attributes"
    form_attributes = ['thumbnail', 'black-and-white']

    class Meta:
        model = Image
        strategy = factory.BUILD_STRATEGY

Dziedziczenie fabryk

Po zdefiniowaniu „bazowej” fabryki dla danej klasy, alternatywne wersje mogą być łatwo zdefiniowane poprzez podklasę. Podklasowana Fabryka dziedziczy wszystkie deklaracje od rodzica i aktualizuje je własnymi deklaracjami.

class UserFactory(factory.Factory):
    class Meta:
        model = base.User

    firstname = "John"
    lastname = "Doe"
    group = 'users'

class AdminFactory(UserFactory):
    admin = True
    group = 'admins'
>>> user = UserFactory()
>>> user
<User: John Doe>
>>> user.group
'users'

>>> admin = AdminFactory()
>>> admin
<User: John Doe (admin)>
>>> admin.group  # The AdminFactory field has overridden the base field
'admins'

Pole ForeignKey

Jeśli atrybut jest złożonym polem (np. ForeignKey do innego modelu), należy użyć deklaracji SubFactory.

# models.py
class User(models.Model):
    first_name = models.CharField()
    group = models.ForeignKey(Group)


# factories.py
import factory
from . import models

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

    first_name = factory.Sequence(lambda n: "Agent %03d" % n)
    group = factory.SubFactory(GroupFactory)

Jeśli wartości klucza ForeignKey muszą zostać wybrane z już wypełnionej tabeli (np. django.contrib.contenttypes.models.ContentType), należy użyć fabryki.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())

Odwrotne relacje ForeignKey

Jeśli obiekt powiązany powinien zostać utworzony podczas tworzenia obiektu (np. odwrócona relacja ForeignKey z innego Modelu), należy użyć deklaracji RelatedFactory.

# models.py
class User(models.Model):
    pass

class UserLog(models.Model):
    user = models.ForeignKey(User)
    action = models.CharField()


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

    log = factory.RelatedFactory(UserLogFactory, 'user', action=models.UserLog.ACTION_CREATE)

Po utworzeniu instancji UserFactory, pole factory_boy wywoła UserLogFactory(user=that_user, action=...) tuż przed zwróceniem utworzonego użytkownika.

Pole ManyToMany

Zbudowanie odpowiedniego połączenia między dwoma modelami zależy w dużej mierze od przypadku użycia. factory_boy niestety nie zapewnia narzędzia działającego w podobniy sposób jak w przypadku SubFactory lub RelatedFactory, dlatego programista musi tworzyć własne zależności od modelu. Aby utworzyć relację M2M należy wykorzystać hook post_generation.

# models.py
class Group(models.Model):
    name = models.CharField()

class User(models.Model):
    name = models.CharField()
    groups = models.ManyToManyField(Group)


# factories.py
class GroupFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.Group

    name = factory.Sequence(lambda n: "Group #%s" % n)

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

    name = "John Doe"

    @factory.post_generation
    def groups(self, create, extracted, **kwargs):
        if not create:
            # Simple build, do nothing.
            return

        if extracted:
            # A list of groups were passed in, use them
            for group in extracted:
                self.groups.add(group)

Podczas wywoływania funkcji UserFactory() lub UserFactory.build() nie zostanie utworzone powiązanie z grupą. Natomiast po wywołaniu UserFactory.create(groups=(group1, group2, group3)) deklaracja groups doda przekazane grupy do użytkownika.

class ClinicFactory(factory.django.DjangoModelFactory):
    name = 'Some name'

    street = factory.Faker('street_name')
    postal_code = factory.Faker('postcode')
    place = factory.Faker('city')
    voivodship = factory.Faker('region')
    country = 'Polska'

    @factory.post_generation
    def domains(self, create, data=None, **kwargs):
        if not create:
            return

        if data is None:
            data = 1

        if isinstance(data, int):
            domain_factory = getattr(DomainFactory, 'create')
            for i in range(data):
                self.domains.add(domain_factory())
        elif data:
            for domain in data:
                self.domains.add(domain)

    class Meta:
        model = 'clinics.Clinic'

Innnym przykładem jest możliwość utworzenia deklaracji która będzie przyjmowała liczbę lub obiekt iterowalny aby utworzyć obiekty powiązane. Nie podając żadnej wartości zostanie utworzony i dołączony 1 obiekt DomainFactory.

Pole ManyToMany (through)

Aby utworzyć relację Many2Many poprzez własną tabelę (throw) należy wykorzystać deklarację RelatedFactory.

# models.py
class User(models.Model):
    name = models.CharField()

class Group(models.Model):
    name = models.CharField()
    members = models.ManyToManyField(User, through='GroupLevel')

class GroupLevel(models.Model):
    user = models.ForeignKey(User)
    group = models.ForeignKey(Group)
    rank = models.IntegerField()


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

    name = "John Doe"

class GroupFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.Group

    name = "Admins"

class GroupLevelFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.GroupLevel

    user = factory.SubFactory(UserFactory)
    group = factory.SubFactory(GroupFactory)
    rank = 1

class UserWithGroupFactory(UserFactory):
    membership = factory.RelatedFactory(GroupLevelFactory, 'user')

class UserWith2GroupsFactory(UserFactory):
    membership1 = factory.RelatedFactory(GroupLevelFactory, 'user', group__name='Group1')
    membership2 = factory.RelatedFactory(GroupLevelFactory, 'user', group__name='Group2')

Niestandardowa metoda tworząca fabrykę

Czasami zachodzi potrzeba aby tworząc fabrykę zachowywała się ona inaczej niż domyślna metoda Model.objects.create(). Aby uzyskać żądane zachowanie należy utworzyć własną metodę klasy _create(...).

class UserFactory(factory.DjangoModelFactory):
    class Meta:
        model = UserenaSignup

    username = "l7d8s"
    email = "my_name@example.com"
    password = "my_password"

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        """Override the default ``_create`` with our custom call."""
        manager = cls._get_manager(model_class)
        # The default would use ``manager.create(*args, **kwargs)``
        return manager.create_user(*args, **kwargs)

Wyłaczanie sygnałów

# foo/factories.py

import factory
import factory.django

from . import models
from . import signals

@factory.django.mute_signals(signals.pre_save, signals.post_save)
class FooFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.Foo
def make_chain():
    with factory.django.mute_signals(signals.pre_save, signals.post_save):
        # pre_save/post_save won't be called here.
        return SomeFactory(), SomeOtherFactory()

Konwertowanie fabryki do słownika

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

    first_name = factory.Sequence(lambda n: "Agent %03d" % n)
    username = factory.Faker('username')
>>> factory.build(dict, FACTORY_CLASS=UserFactory)
{'first_name': "Agent 001", 'username': 'john_doe'}

Inicjalizacja fabryk w pytest

Funkcje dostarczane wraz z pytest-factoryboy pozwalają na używanie fabryk bez ich importowania. Konwencja wykorzystywana do uruchamiania fixture z zarejestrowanej klasy wykorzystuj podkreślenia i małe litery. Najlepszym miejscem rejestracji fabryki jest plik conftest.py.

# tests/factories.py
import factory

class AuthorFactory(factory.Factory):

    class Meta:
        model = Author

class GroupForSuperUserFactory(factory.Factory):

    class Meta:
        model = Group


# tests/conftest.py
from pytest_factoryboy import register
from .factories import AuthorFactory, GroupForSuperUserFactory

register(AuthorFactory)
register(GroupForSuperUserFactory)


# tests/test_models.py
def test_factory_fixture(author_factory):
    author = author_factory(name="Charles Dickens")
    assert author.name == "Charles Dickens"

def test_factory_fixture(group_for_super_user_factory):
    author = group_for_super_user_factory(name="Super Group")
    assert author.name == "Super Group"

Istnieje również możliwość rejestracji modelu pod określoną nazwą wraz z ustawionymi parametrami.

register(BookFactory)  # book
register(BookFactory, "second_book")  # second_book

register(AuthorFactory) # author
register(AuthorFactory, "second_author") # second_author

register(AuthorFactory, "male_author", gender="M", name="John Doe")
register(AuthorFactory, "female_author", gender="F")

register(BookFactory, "other_book")  # other_book, book of another author

@pytest.fixture
def other_book__author(second_author):
    """
    Make the relation of the second_book to another (second) author.
    """
    return second_author

@pytest.fixture
def female_author__name():
    """Override female author name as a separate fixture."""
    return "Jane Doe"

Fabryki w testach

Wykorzystująć fabryki w testach mamy możliwość w dwojaki sposób wykorzystania zarejestrowanego fixture. Pierwszy do podanie pełnej nazwy klasy w konwencji małe litery oraz podkreśleniem np. mając fabrykę GroupForSuperUserFactory należy utworzyć fixture group_for_super_user_factory. W teście będzie to obiekt fabryki, który należy najpierw wywołać aby utworzyć obiekt z właściwymi wartościami.

def test_factory_fixture(group_for_super_user_factory):
    assert isinstance(group_for_super_user, GroupForSuperUserFactory)
    author = group_for_super_user_factory(name="Super Group")
    assert author.name == "Super Group"

Istnieje również druga możliwość, która pozwala na bezpośrednie utworzenie modelu w teście bez tworzenia fabryki. Posiłkując się powyższym przykładem, aby utworzyć model dla fabryki GroupForSuperUserFactory tworzymy fixture, jednak bez nazwy factory, czyli group_for_super_user.

def test_factory_fixture(group_for_super_user):
    assert isinstance(group_for_super_user, Group)
from app.models import Book
from factories import BookFactory

def test_book_factory(book_factory):
    """Factories become fixtures automatically."""
    assert isinstance(book_factory, BookFactory)

def test_book(book):
    """Instances become fixtures automatically."""
    assert isinstance(book, Book)

@pytest.mark.parametrize("book__title", ["PyTest for Dummies"])
@pytest.mark.parametrize("author__name", ["Bill Gates"])
def test_parametrized(book):
    """You can set any factory attribute as a fixture using naming convention."""
    assert book.name == "PyTest for Dummies"
    assert book.author.name == "Bill Gates"

Atrybuty w fixture

Tworząc testy możemy parametryzować utworzone fabryki poprzez wykorzystanie markera parametrize. Aby uaktualnić konkretną wartość musimy wykorzystać podwójne podkreślenie wraz z nazwą pola.

@pytest.mark.parametrize("author__name", ["Bill Gates"])
def test_model_fixture(author):
    assert author.name == "Bill Gates"

Czasami konieczne jest przekazanie instancji innego fixture jako wartości atrybutu do fabryki. Możliwe jest przesłonięcie wygenerowanego urządzenia atrybutów, gdzie żądane wartości mogą być wymagane jako zależność fixture. Istnieje również leniwy wrapper dla fixture, które może być użyte w parametryzacji bez definiowania fixture w module.

import pytest
from pytest_factoryboy import register, LazyFixture

@pytest.mark.parametrize("book__author", [LazyFixture("another_author")])
def test_lazy_fixture_name(book, another_author):
    """Test that book author is replaced with another author by fixture name."""
    assert book.author == another_author


@pytest.mark.parametrize("book__author", [LazyFixture(lambda another_author: another_author)])
def test_lazy_fixture_callable(book, another_author):
    """Test that book author is replaced with another author by callable."""
    assert book.author == another_author


# Can also be used in the partial specialization during the registration.
register(BookFactory, "another_book", author=LazyFixture("another_author"))

Przykłady

Poniżej przykład w jaki sposób utworzyć pole własnego typu, pozwalający fabryce na generyczne tworzenie wartości dla wskazanego pola.

# fuzzy_geo.py
from factory.fuzzy import BaseFuzzyAttribute

class FuzzyPoint(BaseFuzzyAttribute):

    def fuzz(self):
        return Point(random.uniform(-180.0, 180.0), random.uniform(-90.0, 90.0))


# factories.py
from .fuzzy_geo import FuzzyPoint


class UserFactory(factory.django.DjangoModelFactory):
    ...
    last_location = FuzzyPoint()

Poniżej bardziej skomplikowany przykład pokazujący w jaki sposób możemy utworzyć fabrykę dla użytkownika aplikacji.

import random
import datetime
import factory

from faker import Faker
from django.utils.text import slugify
from ..models import User


fake = Faker('pl_PL')


class UserFactory(factory.django.DjangoModelFactory):
    first_name = factory.Faker('first_name')
    last_name = factory.Faker('last_name')
    username = factory.LazyAttribute(
        lambda o: slugify(o.first_name + '.' + o.last_name))
    email = factory.LazyAttribute(
        lambda o: o.username + "@" + fake.free_email_domain())
    password = factory.Faker('password', length=10)
    birthday = factory.Faker('date_between_dates',
                             date_start=datetime.date(1960, 1, 1),
                             date_end=datetime.date(1998, 1, 1))
    gender = factory.LazyAttribute(
        lambda o: random.choice([User.FEMALE, User.MALE]))

    notifications_enabled = True
    region = factory.Faker('region')
    city = factory.Faker('city')
    description = factory.Faker('sentences')
    level = 1
    registration_status = 2
    score = 0

    # brands = factory.LazyAttribute(lambda o: random.choice([]))
    profile_photo = 0
    instagram_url = factory.Faker('uri')

    class Meta:
        model = 'users.User'
        django_get_or_create = ('username',)

    @factory.lazy_attribute
    def date_joined(self):
        return datetime.datetime.now() - datetime.timedelta(
            days=random.randint(5, 50))

    last_login = factory.LazyAttribute(
        lambda o: o.date_joined + datetime.timedelta(days=4))

    is_staff = False
    is_active = True
    is_superuser = False