Package: pytest-factoryboy
factory_boyintegration with thepytestrunnerpytest-factoryboy makes it easy to combinefactoryapproach to the test setup with thedependencyinjection,
heart of thepytest fixtures.Install pytest-factoryboypip install pytest-factoryboyConceptLibrary exports a function to register factories as fixtures. Fixtures are contributed
to the same module where register function is called.Model FixtureModel fixture implements an instance of a model created by the factory. Name convention is model’s lowercase-underscore
class name.importfactoryfrompytest_factoryboyimportregister@registerclassAuthorFactory(factory.Factory):classMeta:model=Authorname="Charles Dickens"deftest_model_fixture(author):assertauthor.name=="Charles Dickens"Attributes are FixturesThere are fixtures created automatically for factory attributes. Attribute names are prefixed with the model fixture name and
double underscore (similar to the convention used by factory_boy).@pytest.mark.parametrize("author__name",["Bill Gates"])deftest_model_fixture(author):assertauthor.name=="Bill Gates"Multiple fixturesModel fixtures can be registered with specific names. For example, if you address instances of some collection
by the name like “first”, “second” or of another parent as “other”:register(AuthorFactory)# authorregister(AuthorFactory,"second_author")# second_author@register# book@register(_name="second_book")# second_book@register(_name="other_book")# other_book, book of another authorclassBookFactory(factory.Factory):classMeta:model=Book@pytest.fixturedefother_book__author(second_author):"""Make the relation of the `other_book.author` to `second_author`."""returnsecond_authordeftest_book_authors(book,second_book,other_book,author,second_author):assertbook.author==second_book.author==authorassertother_book.author==second_authorSubFactorySub-factory attribute points to the model fixture of the sub-factory.
Attributes of sub-factories are injected as dependencies to the model fixture and can beoverriddenvia
the parametrization.Related FactoryRelated factory attribute points to the model fixture of the related factory.
Attributes of related factories are injected as dependencies to the model fixture and can beoverriddenvia
the parametrization.post-generationPost-generation attribute fixture implements only the extracted value for the post generation function.Factory Fixturepytest-factoryboyalso registers factory fixtures, to allow their use without importing them. The fixture name convention is to use the lowercase-underscore form of the class name.importfactoryfrompytest_factoryboyimportregisterclassAuthorFactory(factory.Factory):classMeta:model=Authorregister(AuthorFactory)# => author_factorydeftest_factory_fixture(author_factory):author=author_factory(name="Charles Dickens")assertauthor.name=="Charles Dickens"IntegrationAn example offactory_boyandpytestintegration.# tests/factories.pyimportfactoryfromappimportmodelsfromfakerimportFactoryasFakerFactoryfaker=FakerFactory.create()classAuthorFactory(factory.django.DjangoModelFactory):classMeta:model=models.Authorname=factory.LazyFunction(lambda:faker.name())classBookFactory(factory.django.DjangoModelFactory):classMeta:model=models.Booktitle=factory.LazyFunction(lambda:faker.sentence(nb_words=4))author=factory.SubFactory(AuthorFactory)# tests/conftest.pyfrompytest_factoryboyimportregisterfrom.importfactoriesregister(factories.AuthorFactory)register(factories.BookFactory)# tests/test_models.pyfromapp.modelsimportBookfrom.factoriesimportBookFactorydeftest_book_factory(book_factory):"""Factories become fixtures automatically."""assertbook_factoryisBookFactorydeftest_book(book):"""Instances become fixtures automatically."""assertisinstance(book,Book)@pytest.mark.parametrize("book__title",["PyTest for Dummies"])@pytest.mark.parametrize("author__name",["Bill Gates"])deftest_parametrized(book):"""You can set any factory attribute as a fixture using naming convention."""assertbook.title=="PyTest for Dummies"assertbook.author.name=="Bill Gates"Fixture partial specializationThere is a possibility to pass keyword parameters in order to override factory attribute values during fixture
registration. This comes in handy when your test case is requesting a lot of fixture flavors. Too much for the
regular pytest parametrization.
In this case, you can register fixture flavors in the local test module and specify value deviations insideregisterfunction calls.register(AuthorFactory,"male_author",gender="M",name="John Doe")register(AuthorFactory,"female_author",gender="F")@pytest.fixturedeffemale_author__name():"""Override female author name as a separate fixture."""return"Jane Doe"@pytest.mark.parametrize("male_author__age",[42])# Override even moredeftest_partial(male_author,female_author):"""Test fixture partial specialization."""assertmale_author.gender=="M"assertmale_author.name=="John Doe"assertmale_author.age==42assertfemale_author.gender=="F"assertfemale_author.name=="Jane Doe"Fixture attributesSometimes it is necessary to pass an instance of another fixture as an attribute value to the factory.
It is possible to override the generated attribute fixture where desired values can be requested as
fixture dependencies. There is also a lazy wrapper for the fixture that can be used in the parametrization
without defining fixtures in a module.LazyFixture constructor accepts either existing fixture name or callable with dependencies:importpytestfrompytest_factoryboyimportregister,LazyFixture@pytest.mark.parametrize("book__author",[LazyFixture("another_author")])deftest_lazy_fixture_name(book,another_author):"""Test that book author is replaced with another author by fixture name."""assertbook.author==another_author@pytest.mark.parametrize("book__author",[LazyFixture(lambdaanother_author:another_author)])deftest_lazy_fixture_callable(book,another_author):"""Test that book author is replaced with another author by callable."""assertbook.author==another_author# Can also be used in the partial specialization during the registration.register(BookFactory,"another_book",author=LazyFixture("another_author"))Generic container classes as modelsIt’s often useful to create factories fordictor other common generic container classes.
In that case, you should wrap the container class aroundnamed_model(...), so that pytest-factoryboy can correctly determine the model name when using it in a SubFactory or RelatedFactory.Pytest-factoryboy will otherwise raise a warning.For example:importfactoryfrompytest_factoryboyimportnamed_model,register@registerclassJSONPayload(factory.Factory):classMeta:model=named_model("JSONPayload",dict)name="foo"deftest_foo(json_payload):assertjson_payload.name=="foo"As a bonus, factory is automatically registering thejson_payloadfixture (rather thandict), so there is no need to override@register(_name="json_payload")).Post-generation dependenciesUnlike factory_boy which binds related objects using an internal container to store results of lazy evaluations,
pytest-factoryboy relies on the PyTest request.Circular dependencies between objects can be resolved using post-generation hooks/related factories in combination with
passing the SelfAttribute, but in the case of PyTest request fixture functions have to return values in order to be cached
in the request and to become available to other fixtures.That’s why evaluation of the post-generation declaration in pytest-factoryboy is deferred until calling
the test function.
This solves circular dependency resolution for situations like:o->[ A ]-->[ B ]<--[ C ]-o
|                        |
o----(C depends on A)----oOn the other hand, deferring the evaluation of post-generation declarations evaluation makes their result unavailable during the generation
of objects that are not in the circular dependency, but they rely on the post-generation action.pytest-factoryboy is trying to detect cycles and resolve post-generation dependencies automatically.frompytest_factoryboyimportregisterclassFoo(object):def__init__(self,value):self.value=valueclassBar(object):def__init__(self,foo):self.foo=foo@registerclassFooFactory(factory.Factory):classMeta:model=Foovalue=0@factory.post_generationdefset1(foo,create,value,**kwargs):foo.value=1@registerclassBarFactory(factory.Factory):classMeta:model=Barfoo=factory.SubFactory(FooFactory)@classmethoddef_create(cls,model_class,foo):assertfoo.value==1# Assert that set1 is evaluated before object generationreturnsuper(BarFactory,cls)._create(model_class,foo=foo)# Forces 'set1' to be evaluated first.deftest_depends_on_set1(bar):"""Test that post-generation hooks are done and the value is 2."""assertbar.foo.value==1Hookspytest-factoryboy exposes severalpytest hookswhich might be helpful for e.g. controlling database transaction, for reporting etc:pytest_factoryboy_done(request) - Called after all factory-based fixtures and their post-generation actions have been evaluated.LicenseThis software is licensed under theMIT license.© 2015 Oleg Pidsadnyi, Anatoly Bubenkov and others