factory_boy provides custom Factory subclasses for various ORMs, adding dedicated features.
The first versions of factory_boy were designed specifically for Django, but the library has now evolved to be framework-independant.
Most features should thus feel quite familiar to Django users.
All factories for a Django Model should use the DjangoModelFactory base class.
Dedicated class for Django Model factories.
This class provides the following features:
The class Meta on a DjangoModelFactory supports extra parameters:
New in version 2.5.0.
All queries to the related model will be routed to the given database. It defaults to 'default'.
New in version 2.4.0.
Fields whose name are passed in this list will be used to perform a Model.objects.get_or_create() instead of the usual Model.objects.create():
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = 'myapp.User' # Equivalent to ``model = myapp.models.User``
django_get_or_create = ('username',)
username = 'john'
>>> User.objects.all()
[]
>>> 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>]
Note
If a DjangoModelFactory relates to an abstract model, be sure to declare the DjangoModelFactory as abstract:
class MyAbstractModelFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.MyAbstractModel
abstract = True
class MyConcreteModelFactory(MyAbstractModelFactory):
class Meta:
model = models.MyConcreteModel
Otherwise, factory_boy will try to get the ‘next PK’ counter from the abstract model.
Custom declarations for django.db.models.FileField
Parameters: |
|
---|
Note
If the value None was passed for the FileField field, this will disable field generation:
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
Custom declarations for django.db.models.ImageField
Parameters: |
|
---|
Note
If the value None was passed for the FileField field, this will disable field generation:
Note
Just as Django’s django.db.models.ImageField requires the Python Imaging Library, this ImageField requires it too.
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
Signals are often used to plug some custom code into external components code; for instance to create Profile objects on-the-fly when a new User object is saved.
This may interfere with finely tuned factories, which would create both using RelatedFactory.
To work around this problem, use the mute_signals() decorator/context manager:
Disable the list of selected signals when calling the factory, and reactivate them upon leaving.
# 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()
factory_boy supports Mogo-style models, through the MogoFactory class.
Mogo is a wrapper around the pymongo library for MongoDB.
factory_boy supports MongoEngine-style models, through the MongoEngineFactory class.
mongoengine is a wrapper around the pymongo library for MongoDB.
Dedicated class for MongoEngine models.
This class provides the following features:
Note
If the associated class <factory.FactoryOptions.model is a mongoengine.EmbeddedDocument, the create() function won’t “save” it, since this wouldn’t make sense.
This feature makes it possible to use SubFactory to create embedded document.
Factoy_boy also supports SQLAlchemy models through the SQLAlchemyModelFactory class.
To work, this class needs an SQLAlchemy session object affected to the Meta.sqlalchemy_session attribute.
Dedicated class for SQLAlchemy models.
This class provides the following features:
In addition to the usual parameters available in class Meta, a SQLAlchemyModelFactory also supports the following settings:
SQLAlchemy session to use to communicate with the database when creating an object through this SQLAlchemyModelFactory.
A (very) simple example:
from sqlalchemy import Column, Integer, Unicode, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
engine = create_engine('sqlite://')
session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
class User(Base):
""" A SQLAlchemy simple model class who represents a user """
__tablename__ = 'UserTable'
id = Column(Integer(), primary_key=True)
name = Column(Unicode(20))
Base.metadata.create_all(engine)
class UserFactory(SQLAlchemyModelFactory):
class Meta:
model = User
sqlalchemy_session = session # the SQLAlchemy session object
id = factory.Sequence(lambda n: n)
name = factory.Sequence(lambda n: u'User %d' % n)
>>> session.query(User).all()
[]
>>> UserFactory()
<User: User 1>
>>> session.query(User).all()
[<User: User 1>]
Since SQLAlchemy is a general purpose library, there is no “global” session management system.
The most common pattern when working with unit tests and factory_boy is to use SQLAlchemy‘s sqlalchemy.orm.scoping.scoped_session:
Note
See the excellent SQLAlchemy guide on scoped_session for details of scoped_session‘s usage.
The basic idea is that declarative parts of the code (including factories) need a simple way to access the “current session”, but that session will only be created and configured at a later point.
The scoped_session handles this, by virtue of only creating the session when a query is sent to the database.
Here is an example layout:
# myprojet/test/common.py
from sqlalchemy import orm
Session = orm.scoped_session(orm.sessionmaker())
# myproject/factories.py
import factory
import factory.alchemy
from . import models
from .test import common
class UserFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = models.User
# Use the not-so-global scoped_session
# Warning: DO NOT USE common.Session()!
sqlalchemy_session = common.Session
name = factory.Sequence(lambda n: "User %d" % n)
# myproject/test/runtests.py
import sqlalchemy
from . import common
def runtests():
engine = sqlalchemy.create_engine('sqlite://')
# It's a scoped_session, and now is the time to configure it.
common.Session.configure(bind=engine)
run_the_tests
# myproject/test/test_stuff.py
import unittest
from . import common
class MyTest(unittest.TestCase):
def setUp(self):
# Prepare a new, clean session
self.session = common.Session()
def test_something(self):
u = factories.UserFactory()
self.assertEqual([u], self.session.query(User).all())
def tearDown(self):
# Rollback the session => no changes to the database
self.session.rollback()
# Remove it, so that the next test gets a new Session()
common.Session.remove()