factory
factory_boy provides custom Factory
subclasses for various ORMs, adding dedicated features.
factory.django
The first versions of factory_boy were designed specifically for Django, but the library has now evolved to be framework-independent.
Most features should thus feel quite familiar to Django users.
All factories for a Django ~django.db.models.Model
should use the DjangoModelFactory
base class.
Dedicated class for Django ~django.db.models.Model
factories.
This class provides the following features:
- The
~factory.FactoryOptions.model
attribute also supports the'app.Model'
syntax ~factory.Factory.create()
usesModel.objects.create() <django.db.models.query.QuerySet.create>
- When using
~factory.RelatedFactory
or~factory.PostGeneration
attributes, the base object will besaved <django.db.models.Model.save>
once all post-generation hooks have run.
Note
With Django versions 1.8.0 to 1.8.3, it was no longer possible to call .build()
on a factory if this factory used a ~factory.SubFactory
pointing to another model: Django refused to set a ~djang.db.models.ForeignKey
to an unsaved ~django.db.models.Model
instance.
See https://code.djangoproject.com/ticket/10811 and https://code.djangoproject.com/ticket/25160 for details.
The class Meta
on a ~DjangoModelFactory
supports extra parameters:
database
2.5.0
All queries to the related model will be routed to the given database. It defaults to 'default'
.
django_get_or_create
2.4.0
Fields whose name are passed in this list will be used to perform a Model.objects.get_or_create() <django.db.models.query.QuerySet.get_or_create>
instead of the usual Model.objects.create() <django.db.models.query.QuerySet.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>]
Custom declarations for django.db.models.FileField
__init__(self, from_path='', from_file='', data=b'', filename='example.dat')
- param str from_path
Use data from the file located at
from_path
, and keep its filename- param file from_file
Use the contents of the provided file object; use its filename if available, unless
filename
is also provided.- param func from_func
Use function that returns a file object
- param bytes data
Use the provided bytes as file contents
- param str filename
The filename for the FileField
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
__init__(self, from_path='', from_file='', filename='example.jpg', width=100, height=100, color='green', format='JPEG')
- param str from_path
Use data from the file located at
from_path
, and keep its filename- param file from_file
Use the contents of the provided file object; use its filename if available
- param func from_func
Use function that returns a file object
- param str filename
The filename for the ImageField
- param int width
The width of the generated image (default:
100
)- param int height
The height of the generated image (default:
100
)- param str color
The color of the generated image (default:
'green'
)- param str format
The image format (as supported by PIL) (default:
'JPEG'
)
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 <DjangoModelFactory>
, which would create both using ~factory.RelatedFactory
.
To work around this problem, use the mute_signals()
decorator/context manager:
mute_signals(signal1, ...)
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.mogo
factory_boy supports Mogo-style models, through the MogoFactory
class.
Mogo is a wrapper around the pymongo
library for MongoDB.
Dedicated class for Mogo models.
This class provides the following features:
~factory.Factory.build()
calls a model'snew()
method~factory.Factory.create()
builds an instance throughnew()
then saves it.
factory.mongoengine
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:
~factory.Factory.build()
calls a model's__init__
method~factory.Factory.create()
builds an instance through__init__
then saves it.
Note
If the associated class <factory.FactoryOptions.model
is a mongoengine.EmbeddedDocument
, the ~MongoEngineFactory.create
function won't "save" it, since this wouldn't make sense.
This feature makes it possible to use ~factory.SubFactory
to create embedded document.
A minimalist example:
import mongoengine
class Address(mongoengine.EmbeddedDocument):
street = mongoengine.StringField()
class Person(mongoengine.Document):
name = mongoengine.StringField()
address = mongoengine.EmbeddedDocumentField(Address)
import factory
class AddressFactory(factory.mongoengine.MongoEngineFactory):
class Meta:
model = Address
street = factory.Sequence(lambda n: 'street%d' % n)
class PersonFactory(factory.mongoengine.MongoEngineFactory):
class Meta:
model = Person
name = factory.Sequence(lambda n: 'name%d' % n)
address = factory.SubFactory(AddressFactory)
factory.alchemy
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 <SQLAlchemyOptions.sqlalchemy_session>
attribute.
Dedicated class for SQLAlchemy models.
This class provides the following features:
~factory.Factory.create()
usessqlalchemy.orm.session.Session.add
In addition to the usual parameters available in class Meta <factory.base.FactoryOptions>
, a SQLAlchemyModelFactory
also supports the following settings:
sqlalchemy_session
SQLAlchemy session to use to communicate with the database when creating an object through this SQLAlchemyModelFactory
.
sqlalchemy_session_persistence
Control the action taken by sqlalchemy session at the end of a create call.
Valid values are:
None
: do nothing'flush'
: perform a session~sqlalchemy.orm.session.Session.flush
'commit'
: perform a session~sqlalchemy.orm.session.Session.commit
The default value is None
.
If force_flush
is set to True
, it overrides this option.
force_flush
Force a session flush()
at the end of ~factory.alchemy.SQLAlchemyModelFactory._create()
.
Note
This option is deprecated. Use sqlalchemy_session_persistence
instead.
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)
import factory
class UserFactory(factory.alchemy.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
:
- The test runner configures some project-wide
~sqlalchemy.orm.scoping.scoped_session
- Each
~SQLAlchemyModelFactory
subclass uses this~sqlalchemy.orm.scoping.scoped_session
as its~SQLAlchemyOptions.sqlalchemy_session
- The
~unittest.TestCase.tearDown
method of tests callsSession.remove <sqlalchemy.orm.scoping.scoped_session.remove>
to reset the session.
Note
See the excellent SQLAlchemy guide on scoped_session <sqlalchemy:unitofwork_contextual>
for details of ~sqlalchemy.orm.scoping.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 ~sqlalchemy.orm.scoping.scoped_session
handles this, by virtue of only creating the session when a query is sent to the database.
Here is an example layout:
- A global (test-only?) file holds the
~sqlalchemy.orm.scoping.scoped_session
:
# myprojet/test/common.py
from sqlalchemy import orm
Session = orm.scoped_session(orm.sessionmaker())
- All factory access it:
# 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)
- The test runner configures the
~sqlalchemy.orm.scoping.scoped_session
when it starts:
# 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
test cases <unittest.TestCase>
use thisscoped_session
, and clear it after each test (for isolation):
# 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()