Package: factory-boy
factory_boy is a fixtures replacement based on thoughtbot’sfactory_bot.As a fixtures replacement tool, it aims to replace static, hard to maintain fixtures
with easy-to-use factories for complex objects.Instead of building an exhaustive test setup with every possible combination of corner cases,factory_boyallows you to use objects customized for the current test,
while only declaring the test-specific fields:classFooTests(unittest.TestCase):deftest_with_factory_boy(self):# We need a 200€, paid order, shipping to australia, for a VIP customerorder=OrderFactory(amount=200,status='PAID',customer__is_vip=True,address__country='AU',)# Run the tests heredeftest_without_factory_boy(self):address=Address(street="42 fubar street",zipcode="42Z42",city="Sydney",country="AU",)customer=Customer(first_name="John",last_name="Doe",phone="+1234",email="john.doe@example.org",active=True,is_vip=True,address=address,)# etc.factory_boy is designed to work well with various ORMs (Django, MongoDB, SQLAlchemy),
and can easily be extended for other libraries.Its main features include:Straightforward declarative syntaxChaining factory calls while retaining the global contextSupport for multiple build strategies (saved/unsaved instances, stubbed objects)Multiple factories per class support, including inheritanceLinksDocumentation:https://factoryboy.readthedocs.io/Repository:https://github.com/FactoryBoy/factory_boyPackage:https://pypi.org/project/factory-boy/Mailing-list:factoryboy@googlegroups.com|https://groups.google.com/forum/#!forum/factoryboyDownloadPyPI:https://pypi.org/project/factory-boy/$pipinstallfactory_boySource:https://github.com/FactoryBoy/factory_boy/$gitclonegit://github.com/FactoryBoy/factory_boy/$pythonsetup.pyinstallUsageNoteThis section provides a quick summary of factory_boy features.
A more detailed listing is available in the full documentation.Defining factoriesFactories declare a set of attributes used to instantiate a Python object.
The class of the object must be defined in themodelfield of aclass Meta:attribute:importfactoryfrom.importmodelsclassUserFactory(factory.Factory):classMeta:model=models.Userfirst_name='John'last_name='Doe'admin=False# Another, different, factory for the same objectclassAdminFactory(factory.Factory):classMeta:model=models.Userfirst_name='Admin'last_name='User'admin=TrueORM integrationfactory_boy integration with Object Relational Mapping (ORM) tools is provided
through specificfactory.Factorysubclasses:Django, withfactory.django.DjangoModelFactoryMogo, withfactory.mogo.MogoFactoryMongoEngine, withfactory.mongoengine.MongoEngineFactorySQLAlchemy, withfactory.alchemy.SQLAlchemyModelFactoryMore details can be found in the ORM section.Using factoriesfactory_boy supports several different instantiation strategies: build, create, and stub:# Returns a User instance that's not saveduser=UserFactory.build()# Returns a saved User instance.# UserFactory must subclass an ORM base class, such as DjangoModelFactory.user=UserFactory.create()# Returns a stub object (just a bunch of attributes)obj=UserFactory.stub()You can use the Factory class as a shortcut for the default instantiation strategy:# Same as UserFactory.create()user=UserFactory()No matter which strategy is used, it’s possible to override the defined attributes by passing keyword arguments:# Build a User instance and override first_name>>>user=UserFactory.build(first_name='Joe')>>>user.first_name"Joe"It is also possible to create a bunch of objects in a single call:>>>users=UserFactory.build_batch(10,first_name="Joe")>>>len(users)10>>>[user.first_nameforuserinusers]["Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe"]Realistic, random valuesDemos look better with random yet realistic values; and those realistic values can also help discover bugs.
For this, factory_boy relies on the excellentfakerlibrary:classRandomUserFactory(factory.Factory):classMeta:model=models.Userfirst_name=factory.Faker('first_name')last_name=factory.Faker('last_name')>>>RandomUserFactory()<User: Lucy Murray>Reproducible random valuesThe use of fully randomized data in tests is quickly a problem for reproducing broken builds.
To that purpose, factory_boy provides helpers to handle the random seeds it uses, located in thefactory.randommodule:importfactory.randomdefsetup_test_environment():factory.random.reseed_random('my_awesome_project')# Other setup hereLazy AttributesMost factory attributes can be added using static values that are evaluated when the factory is defined,
but some attributes (such as fields whose value is computed from other elements)
will need values assigned each time an instance is generated.These “lazy” attributes can be added as follows:classUserFactory(factory.Factory):classMeta:model=models.Userfirst_name='Joe'last_name='Blow'email=factory.LazyAttribute(lambdaa:'{}.{}@example.com'.format(a.first_name,a.last_name).lower())date_joined=factory.LazyFunction(datetime.now)>>>UserFactory().email"joe.blow@example.com"NoteLazyAttributecalls the function with the object being constructed as an argument, whenLazyFunctiondoes not send any argument.SequencesUnique values in a specific format (for example, e-mail addresses) can be generated using sequences. Sequences are defined by usingSequenceor the decoratorsequence:classUserFactory(factory.Factory):classMeta:model=models.Useremail=factory.Sequence(lambdan:'person{}@example.com'.format(n))>>>UserFactory().email'person0@example.com'>>>UserFactory().email'person1@example.com'AssociationsSome objects have a complex field, that should itself be defined from a dedicated factories.
This is handled by theSubFactoryhelper:classPostFactory(factory.Factory):classMeta:model=models.Postauthor=factory.SubFactory(UserFactory)The associated object’s strategy will be used:# Builds and saves a User and a Post>>>post=PostFactory()>>>post.idisNone# Post has been 'saved'False>>>post.author.idisNone# post.author has been savedFalse# Builds but does not save a User, and then builds but does not save a Post>>>post=PostFactory.build()>>>post.idisNoneTrue>>>post.author.idisNoneTrueSupport Policyfactory_boysupports active Python versions as well as PyPy3.Python’ssupported versions.Django’ssupported
versions.SQLAlchemy:latest version on PyPI.MongoEngine:latest version on PyPI.Debugging factory_boyDebugging factory_boy can be rather complex due to the long chains of calls.
Detailed logging is available through thefactorylogger.A helper,factory.debug(), is available to ease debugging:withfactory.debug():obj=TestModel2Factory()importlogginglogger=logging.getLogger('factory')logger.addHandler(logging.StreamHandler())logger.setLevel(logging.DEBUG)This will yield messages similar to those (artificial indentation):BaseFactory:Preparing tests.test_using.TestModel2Factory(extra={})LazyStub:Computing values for tests.test_using.TestModel2Factory(two=<OrderedDeclarationWrapper for <factory.declarations.SubFactory object at 0x1e15610>>)SubFactory:Instantiating tests.test_using.TestModelFactory(__containers=(<LazyStub for tests.test_using.TestModel2Factory>,), one=4), create=TrueBaseFactory:Preparing tests.test_using.TestModelFactory(extra={'__containers': (<LazyStub for tests.test_using.TestModel2Factory>,), 'one': 4})LazyStub:Computing values for tests.test_using.TestModelFactory(one=4)LazyStub:Computed values, got tests.test_using.TestModelFactory(one=4)BaseFactory:Generating tests.test_using.TestModelFactory(one=4)LazyStub:Computed values, got tests.test_using.TestModel2Factory(two=<tests.test_using.TestModel object at 0x1e15410>)BaseFactory:Generating tests.test_using.TestModel2Factory(two=<tests.test_using.TestModel object at 0x1e15410>)Contributingfactory_boy is distributed under the MIT License.Issues should be opened throughGitHub Issues; whenever possible, a pull request should be included.
Questions and suggestions are welcome on themailing-list.Development dependencies can be installed in avirtualenvwith:$pipinstall--editable'.[dev]'All pull requests should pass the test suite, which can be launched simply with:$maketestallIn order to test coverage, please use:$makecoverageTo test with a specific framework version, you may use atoxtarget:# list all tox environments$tox--listenvs# run tests inside a specific environment (django/mongoengine/SQLAlchemy are not installed)$tox-epy310# run tests inside a specific environment (django)$tox-epy310-djangomain$tox-epy310-djangomain-postgres# run tests inside a specific environment (alchemy)$tox-epy310-alchemy$tox-epy310-alchemy-postgres# run tests inside a specific environment (mongoengine)$tox-epy310-mongoPackagingFor users interesting in packaging FactoryBoy into downstream distribution channels
(e.g..deb,.rpm,.ebuild), the following tips might be helpful:DependenciesThe package’s run-time dependencies are listed insetup.cfg.
The dependencies useful for building and testing the library are covered by thedevanddocextras.Moreover, all development / testing tasks are driven throughmake(1).BuildingIn order to run the build steps (currently only for docs), run:pythonsetup.pyegg_infomakedocTestingWhen testing for the active Python environment, run the following:maketestNoteYou must make sure that thefactorymodule is importable, as it is imported from
the testing code.