A test fixtures replacement for Python

factory_boy

Latest Version Supported Python versions Wheel status License

factory_boy is a fixtures replacement based on thoughtbot's factory_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_boy allows you to use objects customized for the current test, while only declaring the test-specific fields:

class FooTests(unittest.TestCase):

    def test_with_factory_boy(self):
        # We need a 200€, paid order, shipping to australia, for a VIP customer
        order = OrderFactory(
            amount=200,
            status='PAID',
            customer__is_vip=True,
            address__country='AU',
        )
        # Run the tests here

    def test_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="[email protected]",
            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 syntax
  • Chaining factory calls while retaining the global context
  • Support for multiple build strategies (saved/unsaved instances, stubbed objects)
  • Multiple factories per class support, including inheritance

Links

Download

PyPI: https://pypi.org/project/factory-boy/

$ pip install factory_boy

Source: https://github.com/FactoryBoy/factory_boy/

$ git clone git://github.com/FactoryBoy/factory_boy/
$ python setup.py install

Usage

Note

This section provides a quick summary of factory_boy features. A more detailed listing is available in the full documentation.

Defining factories

Factories declare a set of attributes used to instantiate a Python object. The class of the object must be defined in the model field of a class Meta: attribute:

import factory
from . import models

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

    first_name = 'John'
    last_name = 'Doe'
    admin = False

# Another, different, factory for the same object
class AdminFactory(factory.Factory):
    class Meta:
        model = models.User

    first_name = 'Admin'
    last_name = 'User'
    admin = True

ORM integration

factory_boy integration with Object Relational Mapping (ORM) tools is provided through specific factory.Factory subclasses:

  • Django, with factory.django.DjangoModelFactory
  • Mogo, with factory.mogo.MogoFactory
  • MongoEngine, with factory.mongoengine.MongoEngineFactory
  • SQLAlchemy, with factory.alchemy.SQLAlchemyModelFactory

More details can be found in the ORM section.

Using factories

factory_boy supports several different build strategies: build, create, and stub:

# Returns a User instance that's not saved
user = 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 build 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_name for user in users]
["Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe"]

Realistic, random values

Demos look better with random yet realistic values; and those realistic values can also help discover bugs. For this, factory_boy relies on the excellent faker library:

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

    first_name = factory.Faker('first_name')
    last_name = factory.Faker('last_name')
>>> RandomUserFactory()
<User: Lucy Murray>

Reproducible random values

The 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 the factory.random module:

import factory.random

def setup_test_environment():
    factory.random.reseed_random('my_awesome_project')
    # Other setup here

Lazy Attributes

Most 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:

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

    first_name = 'Joe'
    last_name = 'Blow'
    email = factory.LazyAttribute(lambda a: '{}.{}@example.com'.format(a.first_name, a.last_name).lower())
    date_joined = factory.LazyFunction(datetime.now)
>>> UserFactory().email
"[email protected]"

Note

LazyAttribute calls the function with the object being constructed as an argument, when LazyFunction does not send any argument.

Sequences

Unique values in a specific format (for example, e-mail addresses) can be generated using sequences. Sequences are defined by using Sequence or the decorator sequence:

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

    email = factory.Sequence(lambda n: 'person{}@example.com'.format(n))

>>> UserFactory().email
'[email protected]'
>>> UserFactory().email
'[email protected]'

Associations

Some objects have a complex field, that should itself be defined from a dedicated factories. This is handled by the SubFactory helper:

class PostFactory(factory.Factory):
    class Meta:
        model = models.Post

    author = factory.SubFactory(UserFactory)

The associated object's strategy will be used:

# Builds and saves a User and a Post
>>> post = PostFactory()
>>> post.id is None  # Post has been 'saved'
False
>>> post.author.id is None  # post.author has been saved
False

# Builds but does not save a User, and then builds but does not save a Post
>>> post = PostFactory.build()
>>> post.id is None
True
>>> post.author.id is None
True

Support Policy

factory_boy supports active Python versions as well as PyPy3.

Debugging factory_boy

Debugging factory_boy can be rather complex due to the long chains of calls. Detailed logging is available through the factory logger.

A helper, factory.debug(), is available to ease debugging:

with factory.debug():
    obj = TestModel2Factory()


import logging
logger = 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=True
    BaseFactory: 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>)

Contributing

factory_boy is distributed under the MIT License.

Issues should be opened through GitHub Issues; whenever possible, a pull request should be included. Questions and suggestions are welcome on the mailing-list.

Development dependencies can be installed in a virtualenv with:

$ pip install --editable '.[dev]'

All pull requests should pass the test suite, which can be launched simply with:

$ make testall

In order to test coverage, please use:

$ make coverage

To test with a specific framework version, you may use a tox target:

# list all tox environments
$ tox --listenvs

# run tests inside a specific environment
$ tox -e py36-django20-alchemy13-mongoengine017

Valid options are:

  • DJANGO for Django
  • MONGOENGINE for mongoengine
  • ALCHEMY for SQLAlchemy

To avoid running mongoengine tests (e.g no MongoDB server installed), run:

$ make SKIP_MONGOENGINE=1 test
Owner
FactoryBoy project
Contributors to the factory_boy Python library, and related projects
FactoryBoy project
Comments
  • factory.Trait() cannot get a factory.RelatedFactory override

    factory.Trait() cannot get a factory.RelatedFactory override

    Traits does not seem to allow a related factory override.

    Ex:

    class MyModelFactory(factory.django.DjangoModelFactory):
        class Meta:
              model = models.MyModel
        class Params:
            with_my_other_model = factory.Trait(
                 my_other_model=factory.RelatedFactory(MyOtherModelFactory, 'my_model')
            )
    

    It will complain my_other_model is not a valid field.

    That would be awesome.

  • Allow SQLAlchemy factories to be committed on create

    Allow SQLAlchemy factories to be committed on create

    It seems like the general consensus is that one should only flush the current session during a test case, and roll back on each tear down. Due to the nature of my testing setup it is more convenient to commit instead of flush on create. Have you considered adding a meta flag similar to force_flush that would allow the session to be committed?

    for example:

    class SQLAlchemyModelFactory(base.Factory):
        """Factory for SQLAlchemy models. """
    
        _options_class = SQLAlchemyOptions
        class Meta:
            abstract = True
    
        @classmethod
        def _create(cls, model_class, *args, **kwargs):
            """Create an instance of the model, and save it to the database."""
            session = cls._meta.sqlalchemy_session
            obj = model_class(*args, **kwargs)
            session.add(obj)
    +       if cls._meta.force_commit:
    +          session.commit()
            elif cls._meta.force_flush:
                session.flush()
            return obj
    

    overriding SQLAlchemyModelFactory._create is my current solution. Any suggestions on how to properly have Factories be committed on creation is welcome! Thanks!

  • SQLAlchemyModelFactory _create should at least flush the session

    SQLAlchemyModelFactory _create should at least flush the session

    SQLAlchemyModelFactory _create (https://github.com/rbarrois/factory_boy/blob/master/factory/alchemy.py#L45-50) should at least flush(I think flush in this context is better than commit) the SQLAlchemy session to produce the behaviour described in the docs:

    >>> session.query(User).all()
    []
    >>> UserFactory()
    <User: User 1>
    >>> session.query(User).all()
    [<User: User 1>]
    
  • Managing randomness with faker is impossible

    Managing randomness with faker is impossible

    In the case when the fields are specified over faker providers, it's impossible to manage randomness because order of evaluation is not fixed (iterating a dict internally?). Example: Seed the random before of instantiation of

    class CompanyFactory(factory.DjangoModelFactory):
        class Meta:
            model = Company
    
        name = faker.company
        status = 'APR'
    

    with

    import factory
    faker = factory.faker.Faker._get_faker(locale='de_DE')
    faker.random.seed(0)
    

    returns the same result. But adding any other fuzzy field, for instance:

    faker.phone_number
    

    makes both fields to be filled in an unpredictable way. Maybe because the fields are evaluated in the dict iteration. It would be nice to have a work around at least.

  • Add peewee support

    Add peewee support

    It was designed around my branch of peewee https://github.com/cam-stitt/peewee. I have a pull request waiting with my minor changes so hopefully it will be upstream soon. I have not yet completed any documentation regarding the implementation, it was merely a first attempt at getting one working for my own project.

    Edit: My changes have been merged into master for peewee. All tests now pass on master.

  • DjangoModelFactory's

    DjangoModelFactory's "_setup_next_sequence" assumes that pk is an integer

    Offending code is here:

        @classmethod
        def _setup_next_sequence(cls):
            """Compute the next available PK, based on the 'pk' database field."""
    
            model = cls._associated_class  # pylint: disable=E1101
            manager = cls._get_manager(model)
    
            try:
                return 1 + manager.values_list('pk', flat=True
                    ).order_by('-pk')[0]
            except IndexError:
                return 1
    

    This problem didn't exist in factory_boy 1.3. My field that was using a non-integer PK is using a sequence, which worked fine previously:

    code = factory.Sequence(lambda n: str(n).zfill(3))
    

    I haven't dug into the code enough to know much about the changes that caused this problem, but ultimately I'd like to be able to use the sequence for the field again.

  • faker.py does not install via pip - strange stuff!!

    faker.py does not install via pip - strange stuff!!

    Hi guys,

    now this one is really strange. I just wanted to try the build-in faker and basically I am getting an attribute not found error:

    >>> import factory
    >>> factory.Faker('email')
    Traceback (most recent call last):
      File "<console>", line 1, in <module>
    AttributeError: 'module' object has no attribute 'Faker'
    

    20 minutes later I had a look at the virtualenv/site-packages path and noticed that the file "faker.py" is missing.

    And after doing an...

    pip uninstall factory-boy
    pip install factory-boy
    

    it is still missing!

    Any ideas whats going wrong?

  • bug with django 4.1

    bug with django 4.1

    Description

    we are using factory boy with django unit test but when we upgraded from 4.0.4 to 4.1.1 i got the following errors

    Traceback (most recent call last):
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/models/query.py", line 928, in get_or_create
        return self.get(**kwargs), False
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/cacheops/query.py", line 353, in get
        return qs._no_monkey.get(qs, *args, **kwargs)
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/models/query.py", line 650, in get
        raise self.model.DoesNotExist(
    src.core.timezone.models.TimeZone.DoesNotExist: TimeZone matching query does not exist.
    During handling of the above exception, another exception occurred:
    Traceback (most recent call last):
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/backends/utils.py", line 89, in _execute
        return self.cursor.execute(sql, params)
    psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "core_timezone_pkey"
    DETAIL:  Key (id)=(4) already exists.
    The above exception was the direct cause of the following exception:
    Traceback (most recent call last):
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/django.py", line 143, in _get_or_create
        instance, _created = manager.get_or_create(*args, **key_fields)
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/models/manager.py", line 85, in manager_method
        return getattr(self.get_queryset(), name)(*args, **kwargs)
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/models/query.py", line 935, in get_or_create
        return self.create(**params), True
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/models/query.py", line 671, in create
        obj.save(force_insert=True, using=self.db)
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/models/base.py", line 831, in save
        self.save_base(
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/models/base.py", line 882, in save_base
        updated = self._save_table(
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/models/base.py", line 1025, in _save_table
        results = self._do_insert(
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/models/base.py", line 1066, in _do_insert
        return manager._insert(
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/models/manager.py", line 85, in manager_method
        return getattr(self.get_queryset(), name)(*args, **kwargs)
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/models/query.py", line 1790, in _insert
        return query.get_compiler(using=using).execute_sql(returning_fields)
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1657, in execute_sql
        cursor.execute(sql, params)
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/backends/utils.py", line 103, in execute
        return super().execute(sql, params)
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/cacheops/transaction.py", line 98, in execute
        result = self._no_monkey.execute(self, sql, params)
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/backends/utils.py", line 67, in execute
        return self._execute_with_wrappers(
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/backends/utils.py", line 80, in _execute_with_wrappers
        return executor(sql, params, many, context)
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/backends/utils.py", line 89, in _execute
        return self.cursor.execute(sql, params)
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/utils.py", line 91, in __exit__
        raise dj_exc_value.with_traceback(traceback) from exc_value
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/db/backends/utils.py", line 89, in _execute
        return self.cursor.execute(sql, params)
    django.db.utils.IntegrityError: duplicate key value violates unique constraint "core_timezone_pkey"
    DETAIL:  Key (id)=(4) already exists.
    
    During handling of the above exception, another exception occurred:
    Traceback (most recent call last):
      File "/opt/atlassian/pipelines/agent/build/.venv/lib/python3.9/site-packages/django/test/testcases.py", line 1448, in setUpClass
        cls.setUpTestData()
      File "/opt/atlassian/pipelines/agent/build/adpp_backend/src/tests.py", line 47, in setUpTestData
        cls._admin_user = cls._create_user()
      File "/opt/atlassian/pipelines/agent/build/adpp_backend/src/tests.py", line 41, in _create_user
        return AdminUserFactory()
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/base.py", line 40, in __call__
        return cls.create(**kwargs)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/base.py", line 528, in create
        return cls._generate(enums.CREATE_STRATEGY, kwargs)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/django.py", line 120, in _generate
        return super()._generate(strategy, params)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/base.py", line 465, in _generate
        return step.build()
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/builder.py", line 260, in build
        step.resolve(pre)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/builder.py", line 201, in resolve
        self.attributes[field_name] = getattr(self.stub, field_name)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/builder.py", line 346, in __getattr__
        value = value.evaluate_pre(
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/declarations.py", line 48, in evaluate_pre
        return self.evaluate(instance, step, context)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/declarations.py", line 411, in evaluate
        return step.recurse(subfactory, extra, force_sequence=force_sequence)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/builder.py", line 218, in recurse
        return builder.build(parent_step=self, force_sequence=force_sequence)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/builder.py", line 260, in build
        step.resolve(pre)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/builder.py", line 201, in resolve
        self.attributes[field_name] = getattr(self.stub, field_name)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/builder.py", line 346, in __getattr__
        value = value.evaluate_pre(
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/declarations.py", line 48, in evaluate_pre
        return self.evaluate(instance, step, context)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/declarations.py", line 411, in evaluate
        return step.recurse(subfactory, extra, force_sequence=force_sequence)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/builder.py", line 218, in recurse
        return builder.build(parent_step=self, force_sequence=force_sequence)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/builder.py", line 260, in build
        step.resolve(pre)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/builder.py", line 201, in resolve
        self.attributes[field_name] = getattr(self.stub, field_name)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/builder.py", line 346, in __getattr__
        value = value.evaluate_pre(
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/declarations.py", line 48, in evaluate_pre
        return self.evaluate(instance, step, context)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/declarations.py", line 411, in evaluate
        return step.recurse(subfactory, extra, force_sequence=force_sequence)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/builder.py", line 218, in recurse
        return builder.build(parent_step=self, force_sequence=force_sequence)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/builder.py", line 264, in build
        instance = self.factory_meta.instantiate(
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/base.py", line 317, in instantiate
        return self.factory._create(model, *args, **kwargs)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/django.py", line 166, in _create
        return cls._get_or_create(model_class, *args, **kwargs)
      File "/opt/atlassian/pipelines/agent/build/.venv/src/factory-boy/factory/django.py", line 147, in _get_or_create
        for lookup, value in cls._original_params.items()
    AttributeError: type object 'TimeZoneFactory' has no attribute '_original_params'
    

    To Reproduce

    Share how the bug happened:

    Model / Factory code
    # Include your factories and models here
    import factory
    from factory.faker import faker
    
    from .models import TimeZone
    
    fake = faker.Faker()
    
    
    class TimeZoneFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = TimeZone
    
        name = factory.Sequence(lambda n: f'{fake.name()}_{n}')
    
    #models
    
    class TimeZone(BaseModel):
        """
        TimeZone
        """
    
        class Meta:
            db_table = 'core_timezone'
            verbose_name = _('time zone')
            verbose_name_plural = _('time zones')
    
        name = models.TextField(_('name'), unique=True)
    
    
    The issue

    this factory is used everywhere in the project, not sure why this error appeared after upgrading to django 4.1.1

  • ImportError: The ``fake-factory`` package is now called ``Faker``.

    ImportError: The ``fake-factory`` package is now called ``Faker``.

    Trying to import factory, but it looks like since yesterday, one of the required packages has changed it's name:

        import factory
      File "/venv/lib64/python3.5/site-packages/factory/__init__.py", line 46, in <module>
        from .faker import Faker
      File "/venv/lib64/python3.5/site-packages/factory/faker.py", line 41, in <module>
        import faker
      File "/venv/lib64/python3.5/site-packages/faker/__init__.py", line 7, in <module>
        raise ImportError(error)
    ImportError: The ``fake-factory`` package is now called ``Faker``.
    

    Edit: I now see there's already a commit to fix this. Any chance of a release to pypi?

  • How to handle 'self' Foreign key relation in factory boy

    How to handle 'self' Foreign key relation in factory boy

    Hello,

    I've a django model where i'm using Foreign key with itself. Can you please guide me how i can replicate self relation in factory boy class. e.g.

    class A(models.Model):
        name = models.CharField(max_length=255)
        parent = models.ForeignKey('self', blank=True, null=True)
    

    Please guide how i can create a factory for such type of relations.

    Thanks.

  • Include session getter support

    Include session getter support

    As our team tries to migrate our tests to use factory_boy, we found tricky to set the DB sessions during the tests runtime, mocking was tricky and nasty. I found this issue, which closely resemble the problem we are currently facing, and decided to give a shot.

    The idea is to allow passing a callable returning the SQLAlchemy session in a Meta attribute called sqlalchemy_session_factory. This has several use-cases, allowing control of the session on runtime.

    This is my first OSS contribution ever, super excited with it. Any feedback is appreciated :)

    Fixes https://github.com/FactoryBoy/factory_boy/issues/304

  • Is subclassing `factory.base.Factory` for custom ORM supported?

    Is subclassing `factory.base.Factory` for custom ORM supported?

    The documentation does not mention a supported way to add a factory with custom _create and _build functions. Am I supposed to subclass factory.base.Factory?

    This works:

    from factory import base
    
    class BaseFactory(base.Factory):
        class Meta:
            abstract = True
    
        @classmethod
        def _build(cls, model_class, *args, **kwargs):
            return model_class(*args, **kwargs)
    
        @classmethod
        def _create(cls, model_class, *args, **kwargs):
            # TODO Use my ORM
            return model_class(*args, **kwargs)
    

    (Taken from https://github.com/FactoryBoy/factory_boy/blob/master/factory/mogo.py)

  • DictFactory does not override _setup_next_sequence

    DictFactory does not override _setup_next_sequence

    Description

    Overriding _setup_next_sequence does not work when subclassing DictFactory, but I get it to work for other factories subclassing DjangoModelFactory.

    Model / Factory code
    import factory
    
    
    class TestDictFactory(factory.DictFactory):
        @classmethod
        def _setup_next_sequence(cls) -> int:
            return 10
    
        number = factory.Sequence(lambda n: n)
    

    Test case

    A simple test case where the sequence should start from 10

    def test_dict_factory():
        test = TestDictFactory()
        assert test["number"] == 10
    
    Output
    =========================================== test session starts ===========================================
    platform darwin -- Python 3.10.6, pytest-7.2.0, pluggy-1.0.0
    django: ...
    rootdir: ...
    plugins: Faker-15.1.3, mock-3.10.0, django-4.5.2
    collected 61 items / 60 deselected / 1 selected
    
    tests/test_factories.py F                                                                [100%]
    
    ================================================ FAILURES =================================================
    ____________________________________________ test_dict_factory ____________________________________________
    
        def test_dict_factory():
            test = TestDictFactory()
    >       assert test["number"] == 10
    E       assert 0 == 10
    
    tests/test_factories.py:23: AssertionError
    
    
  • Example Django test code references missing member

    Example Django test code references missing member

    https://github.com/FactoryBoy/factory_boy/blob/edb6c2ee76c842ab9d6c5435ef1e63527c877507/examples/django_demo/generic_foreignkey/tests.py#L19 results in Pylance complaining:

    Cannot access member "tag" for type "TaggedUserFactory"
      Member "tag" is unknown
    

    Similarly: https://github.com/FactoryBoy/factory_boy/blob/edb6c2ee76c842ab9d6c5435ef1e63527c877507/examples/django_demo/generic_foreignkey/tests.py#L22 results in Pylance complaining:

    Argument of type "SubFactory" cannot be assigned to parameter "model" of type "Type[Model] | Model" in function "get_for_model"
      Type "SubFactory" cannot be assigned to type "Type[Model] | Model"
        Type "SubFactory" cannot be assigned to type "Type[Model]"
        "SubFactory" is incompatible with "Model"
    

    I poked at a bit, resulting in #988, but I don't use Django much, so still wasn't quite sure what needed to happen to fix these.

  • Support Django 4.1 Asynchronous ORM interface

    Support Django 4.1 Asynchronous ORM interface

    To instantiate a Django's models with factory_boy within an async context, we currently need to wrap it with sync_to_async, e.g. workspace = await sync_to_async(WorkspaceFactory)().

    It would be awesome if DjangoModelFactory could provide a .acreate() method, which either encapsulates the call to sync_to_async or even better, uses the new Asynchronous ORM interface from Django 4.1.

    The latter would automatically benefit from any future development of Django's async ORM.

  • django_get_or_create with SubFactory

    django_get_or_create with SubFactory

    Description

    The django_get_or_create feature is working well when the concerned Factory is used as a BaseFactory but not when it's used as a SubFactory When used as a SubFactory, the following error occurs : AttributeError: type object 'CurrencyFactory' has no attribute '_original_params'

    To Reproduce

    With the below code, the error will occur randomly when the same currency code/name is used.

    To reproduce it systematically, just change the PairFactory with the following line :

    base_currency = factory.SubFactory(CurrencyFactory, name="Bitcoin")
    

    It will force the IntegrityError in the Currency Factory, so it will try the get_or_create feature. And then call the PairFactory() two times and voila.

    E           AttributeError: type object 'CurrencyFactory' has no attribute '_original_params'
    
    ../../../.cache/pypoetry/virtualenvs/belfort-backend-KmGKt2wB-py3.10/lib/python3.10/site-packages/factory/django.py:151: AttributeError
    
    Model / Factory code
    # Models
    class Currency(models.Model):
        name = models.CharField(max_length=100, unique=True)
        symbol = models.CharField(max_length=20, unique=True)
    
    class Pair(models.Model):
        base_currency = models.ForeignKey(
            Currency, on_delete=models.CASCADE, related_name="pairs_as_base"
        )
        quoted_currency = models.ForeignKey(
            Currency, on_delete=models.CASCADE, related_name="pairs_as_quoted"
        )
    
    # Factories
    class CurrencyFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = "Currency"
            django_get_or_create = ("name", "symbol")
    
        name = factory.Faker("cryptocurrency_name")
        symbol = factory.Faker("cryptocurrency_code")
    
    class PairFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = "Pair"
    
        base_currency = factory.SubFactory(CurrencyFactory)
        quoted_currency = factory.SubFactory(CurrencyFactory)
    

    Notes

    I believe this is linked to the _generate() method which is not called for SubFactory, while the _original_params is set only in this method.

A test fixtures replacement for Python

factory_boy factory_boy is a fixtures replacement based on thoughtbot's factory_bot. As a fixtures replacement tool, it aims to replace static, hard t

Feb 8, 2021
Mixer -- Is a fixtures replacement. Supported Django, Flask, SqlAlchemy and custom python objects.

The Mixer is a helper to generate instances of Django or SQLAlchemy models. It's useful for testing and fixture replacement. Fast and convenient test-

Dec 25, 2022
Mixer -- Is a fixtures replacement. Supported Django, Flask, SqlAlchemy and custom python objects.

The Mixer is a helper to generate instances of Django or SQLAlchemy models. It's useful for testing and fixture replacement. Fast and convenient test-

Feb 4, 2021
A set of pytest fixtures to test Flask applications

pytest-flask An extension of pytest test runner which provides a set of useful tools to simplify testing and development of the Flask extensions and a

Dec 23, 2022
A set of pytest fixtures to test Flask applications

pytest-flask An extension of pytest test runner which provides a set of useful tools to simplify testing and development of the Flask extensions and a

Feb 17, 2021
pytest_pyramid provides basic fixtures for testing pyramid applications with pytest test suite

pytest_pyramid pytest_pyramid provides basic fixtures for testing pyramid applications with pytest test suite. By default, pytest_pyramid will create

Dec 4, 2022
It helps to use fixtures in pytest.mark.parametrize

pytest-lazy-fixture Use your fixtures in @pytest.mark.parametrize. Installation pip install pytest-lazy-fixture Usage import pytest @pytest.fixture(p

Dec 24, 2022
Redis fixtures and fixture factories for Pytest.
Redis fixtures and fixture factories for Pytest.

Redis fixtures and fixture factories for Pytest.This is a pytest plugin, that enables you to test your code that relies on a running Redis database. It allows you to specify additional fixtures for Redis process and client.

Dec 23, 2022
Pynguin, The PYthoN General UnIt Test geNerator is a test-generation tool for Python
Pynguin, The PYthoN General UnIt Test geNerator is a test-generation tool for Python

Pynguin, the PYthoN General UnIt test geNerator, is a tool that allows developers to generate unit tests automatically.

Jan 6, 2023
A small automated test structure using python to test *.cpp codes

Get Started Insert C++ Codes Add Test Code Run Test Samples Check Coverages Insert C++ Codes you can easily add c++ files in /inputs directory there i

Aug 3, 2022
a plugin for py.test that changes the default look and feel of py.test (e.g. progressbar, show tests that fail instantly)
a plugin for py.test that changes the default look and feel of py.test (e.g. progressbar, show tests that fail instantly)

pytest-sugar pytest-sugar is a plugin for pytest that shows failures and errors instantly and shows a progress bar. Requirements You will need the fol

Dec 28, 2022
Ab testing - The using AB test to test of difference of conversion rate
Ab testing - The using AB test to test of difference of conversion rate

Facebook recently introduced a new type of offer that is an alternative to the current type of bidding called maximum bidding he introduced average bidding.

Nov 21, 2022
HTTP load generator, ApacheBench (ab) replacement, formerly known as rakyll/boom
HTTP load generator, ApacheBench (ab) replacement, formerly known as rakyll/boom

hey is a tiny program that sends some load to a web application. hey was originally called boom and was influenced from Tarek Ziade's tool at tarekzia

Jan 7, 2023
Green is a clean, colorful, fast python test runner.
Green is a clean, colorful, fast python test runner.

Green -- A clean, colorful, fast python test runner. Features Clean - Low redundancy in output. Result statistics for each test is vertically aligned.

Dec 22, 2022
splinter - python test framework for web applications

splinter - python tool for testing web applications splinter is an open source tool for testing web applications using Python. It lets you automate br

Dec 27, 2022
splinter - python test framework for web applications

splinter - python tool for testing web applications splinter is an open source tool for testing web applications using Python. It lets you automate br

Feb 5, 2021
Parameterized testing with any Python test framework

Parameterized testing with any Python test framework Parameterized testing in Python sucks. parameterized fixes that. For everything. Parameterized te

Dec 21, 2022
Flexible test automation for Python

Nox - Flexible test automation for Python nox is a command-line tool that automates testing in multiple Python environments, similar to tox. Unlike to

Jan 3, 2023
Python wrapper of Android uiautomator test tool.

uiautomator This module is a Python wrapper of Android uiautomator testing framework. It works on Android 4.1+ (API Level 16~30) simply with Android d

Dec 30, 2022