pytest-dev / pytest-factoryboy

factory_boy integration the pytest runner
MIT License
358 stars 41 forks source link

No parameters generated for model-only attributres #23

Open ekohl opened 8 years ago

ekohl commented 8 years ago

No parameters are generated for model-only parameters. We often have Django model defaults which we don't override in factories, but do override in tests. For example:

class Book(models.Model):
    title = models.CharField(max_length=255, default='My title')

class BookFactory(factory.DjangoModelFactory):
    class Meta:
        model = Book

register(BookFactory)

@pytest.mark.django_db
@pytest.mark.parametrize('book__title', ['My other title'])
def test_title(book):
    assert book.title == 'My other title'

This results in ValueError: <function test_title at 0x7efe970a2488> uses no fixture 'book__title'

If I add the title to the BookFactory it does work.

Result of pip freeze:

Django==1.9.6
factory-boy==2.7.0
fake-factory==0.5.7
inflection==0.3.1
ipaddress==1.0.16
py==1.4.31
pytest==2.9.1
pytest-django==2.9.1
pytest-factoryboy==1.1.6
python-dateutil==2.5.3
six==1.10.0
ekohl commented 8 years ago

Note that this is because factory_class.declarations() is used, which only lists explicitly declared parameters.

olegpidsadnyi commented 8 years ago

That's right. I actually started working on a project called alchemyboy time ago. The idea is to generate factories based on the model. pytest-factoryboy can't be responsible for introspection of the Django or SQLAlchemy models. And even if declaration (or fixture) is generated it has to take a default from the ORM model. It is interesting problem, but i think it has to extend FactoryBoy rather than pytest.

ekohl commented 8 years ago

I agree that FactoryBoy needs something similar to declarations() to get all overrideable attributes, like accepted_arguments(). Then it's trivial for pytest-factoryboy to use that and it should work.

olegpidsadnyi commented 8 years ago

You can try fixing it for your project for now. https://github.com/olegpidsadnyi/alchemyboy/blob/master/alchemyboy/base.py#L27

So basically you need a metaclass and a base class for model factories that will generate base attributes with incorporating default column values into LazyAttribute. In the subclasses you can always override it with specific values.

ekohl commented 8 years ago

I don't know SQLAlchemy + FactoryBoy now since we use Django models, but FactoryBoy already accepts the model parameters even if they're not specified on the factory. It's just that pytest-factoryboy doesn't know that and doesn't convert them into fixture parameters.

ekohl commented 8 years ago

I created an issue to extend FactoryBoy.

olegpidsadnyi commented 8 years ago

Do you mean if you have a shallow factory like one above and then if you pass any of the arguments during creation of an object like BookFactory(title="bla") it works? I think it works just because it treats kwargs as declarations as well. Of course declaration has to be in the class somehow before you imperatively call it in order to generate parameters. I don't see FactoryBoy introspecting Django models in their code. But you can do that. You could iterate django columns and add declarations for the columns that have defaults. The example that I sent you I made for SQLA, but shouldn't be much of a difference for Django. This could be very interesting project to contribute to FactoryBoy. Imagine you have rich column types in your ORM. For example if a field represents Email type you can use faker.email to generate realistic values.

ekohl commented 8 years ago

Yes, BookFactory(title="bla") in the above example currently works. That means if you use [field.name for field in model_class._meta.fields] to inspect which fields are available (besides just declarations()) then I suspect the current code in pytest-factoryboy would work. I believe that should be an API call in FactoryBoy.

ekohl commented 8 years ago

This could be very interesting project to contribute to FactoryBoy. Imagine you have rich column types in your ORM. For example if a field represents Email type you can use faker.email to generate realistic values.

That certainly sounds interesting. An AutoFieldFuzzer that inspects the model and selects the best fuzzer sounds very useful.

blueyed commented 7 years ago

https://github.com/FactoryBoy/factory_boy/commit/6f202077a5c8156fe96f8a028f883c14962f5b95 and the commits around it might be relevant?

ekohl commented 7 years ago

Due to these limitations I ended up not using this project and currently I don't work on any Django projects so my need for this no longer exists. Feel free to close this.