pytest-factoryboy icon indicating copy to clipboard operation
pytest-factoryboy copied to clipboard

LazyFixture ignores Subfactory attribute assignment

Open dada-engineer opened this issue 8 years ago • 2 comments

I am using pytest-factoryboy to test some of my django Code.

When using A model 'MainModel' with a ForeignKey to another Model 'OneOnly' I create a Factory for both like the following.

models.py:

from django.db import models


class OnlyOne(models.Model):
    name = models.CharField(max_length=16)


class MainModel(models.Model):
    name = models.CharField(max_length=16)
    one = models.ForeignKey(OnlyOne)

factories.py

import factory
import factory.fuzzy

from bug.models import MainModel
from bug.models import OnlyOne


class OnlyOneFactory(factory.DjangoModelFactory):
    class Meta(object):
        model = OnlyOne

    name = factory.Sequence(lambda n: 'OnlyOne {}'.format(n))


class MainModelFactory(factory.DjangoModelFactory):
    class Meta(object):
        model = MainModel

    name = factory.Sequence(lambda n: 'MainModel {}'.format(n))
    one = factory.SubFactory(OnlyOneFactory, name='Initial Name')

So I want the Subfactory to set OnlyOnes' name always to Initial Name. As of the documentation this is how to do it (see example here ): one = factory.SubFactory(OnlyOneFactory, name='Initial Name')

Now I have written some basic tests to test that the name of OnlyOne is set correctly. And it is if I use the factory build and/or create method, but however not if I use the LazyFixture.

test_factories.py:

"""Test the factories."""
import pytest


@pytest.mark.django_db
def test_main_model_factory__create(main_model_factory):
    """Test if the MainModelFactory.create() works properly."""
    main_model = main_model_factory.create()
    assert main_model.one.name == 'Initial Name'


@pytest.mark.django_db
def test_main_model_factory__build(main_model_factory):
    """Test if the MainModelFactory.build() works properly."""
    main_model = main_model_factory.build()
    assert main_model.one.name == 'Initial Name'


@pytest.mark.django_db
def test_main_model_fixture(main_model):
    """Test if the MainModelFactory shortcut works properly."""
    assert main_model.one.name == 'Initial Name'

Test output is the following:

pytest
=========================================================== test session starts ============================================================
platform linux2 -- Python 2.7.12+, pytest-3.0.3, py-1.4.31, pluggy-0.4.0
django settings: factoryboy_subfactory_attribute_bug.settings (from ini file)
rootdir: /home/daniel/workspace/private/factoryboy_subfactory_attribute_bug/factoryboy_subfactory_attribute_bug, inifile: pytest.ini
plugins: bdd-2.18.0, factoryboy-1.1.6, django-2.9.1, timeout-1.0.0, blockage-0.2.0, cov-2.4.0
collected 3 items 

bug/tests/test_factories.py ..F

================================================================= FAILURES =================================================================
_________________________________________________________ test_main_model_fixture __________________________________________________________

main_model = <MainModel: MainModel object>

    @pytest.mark.django_db
    def test_main_model_fixture(main_model):
        """Test if the MainModelFactory shortcut works properly."""
>       assert main_model.one.name == 'Initial Name'
E       assert 'OnlyOne 2' == 'Initial Name'
E         - OnlyOne 2
E         + Initial Name

bug/tests/test_factories.py:22: AssertionError
==================================================== 1 failed, 2 passed in 0.32 seconds ====================================================

The question now is should it set the name as expected or am I misunderstanding the functionality of the LazyFixture used?

I am attaching a sample.tar.gz with a working django app to simulate this. sample.tar.gz

  1. Unpack and cd to folder
  2. virtualenv env -p python3
  3. pip install django python-factoryboy pytest
  4. run pytest

Thanks for the help.

Edit: If you need any further information please feel free to ask. Sorry if something is missing I am new to submitting Issues on an open source project.

dada-engineer avatar Apr 26 '17 20:04 dada-engineer

For now a workaround is to overwrite the _create method of my factory so it sets the attribute like:

@classmethod
    def _create(cls, model_class, *args, **kwargs):
        obj = super(ArticleFactory, cls)._create(model_class, *args, **kwargs)
        obj.one.name = 'Initial Name'
        obj.one.save()
        return obj

But another database access with the save operation is basically not what we want, is it?

dada-engineer avatar May 08 '17 06:05 dada-engineer

+1

_create method override is so bothersome things

chkdmin avatar May 18 '17 08:05 chkdmin