Pytest-django: Easy fixture loading

Created on 26 Jun 2012  路  10Comments  路  Source: pytest-dev/pytest-django

I noticed that the helper function for loading fixtures was removed. Possibly it wasn't worth keeping around the way it was, but I was wondering if there was scope for something more useful.

The simplest thing would be a function that takes a list of fixtures and loads them. What would be even better would be to be able to use a fixtures variable within a class and have it automatically loaded as with Django's TestCases.

It does seem like a bit of a step backwards from the functionality of Django's built in testing to have to do this:

    def setup_class(self):
        fixtures = ['base.json', 'things.json']

        for fixture in fixtures:
            call_command('loaddata', fixture)

..even if it is just a few lines of code.

What do you think?

Most helpful comment

This works. From https://flowfx.de/blog/populate-your-django-test-database-with-pytest-fixtures/ :

import pytest

from django.core.management import call_command

@pytest.fixture(scope='session')
def django_db_setup(django_db_setup, django_db_blocker):
    with django_db_blocker.unblock():
        call_command('loaddata', 'potatoes_data.json')
# tests/test_models.py

def test_my_potatoes(db, django_db_setup):
    # GIVEN a full database of potatoes, as provided by the django_db_setup fixture
    all_my_potatoes = Potato.objects.all()

All 10 comments

The main reason for why it was removed is that I think the use of fixtures in general should be discouraged. See Carl Meyers (Django core developer) Pycon talk for why (start watching at 16:25): http://www.youtube.com/watch?v=ickNQcNXiS4&feature=player_detailpage#t=986s

If you want to use fixtures despite of its flaws your options are:

1) Do something like the above
2) Create a helper function/mark in your project's conftest.py (should just be a couple of lines which invokes call_command)
3) Use a Django TestCase subclass with the fixtures attribute as usual

Django's testing tools makes some bad choices for testing (fixtures being one). I think we should encourage best practices wherever we have a chance to encourage better testing practices. What is a best practice and what's not is clearly not subjective, but since it is still really easy to use fixtures anyways, I don't think it is that bad to not have any support in pytest-django.

I have personally used object factories (mostly with factory_boy) instead of fixtures in my own projects with great success so far. I would really like to create a "best practices" documentation page with examples, this would be an ideally topic there.

Thoughts?

If fixtures get re-introduced it should probably be as a mark. As @pelme says this should only be a few lines.

Personally I've been manually creating the objects I need inside the test, e.g.: models.User(foo=bar) which I've found clearer so far. If you find youself constructing the same model in a number of tests you can easily create a funcarg:

def pytest_funcarg__myuser(request):
    return models.User(foo=bar)

def test_foo(myuser):
    assert myuser.foo == bar

Finally I'll also plug issue #11 which proposes a "models" funcarg which might make this style of creating items in the database easier.

I just realised that your own funcarg is not the most user friendly with the changes of pull request in #12. If your funcarg is loaded before the database has been initialised it will fail. This is why the admin_client funcarg needs to call django_compat.setup_databases() which you could also call. But it's not ideal.

Again, this is something that the recent funcarg/item merge solves since you can then just request the "djangodb" funcarg and it will be setup.

I am closing this issue, just to be clear, it is still possible to use Django's TestCase subclasses to use the Django fixture loading directly in the test classes.

Current best practices do not advocate fixture loading from files, so it will not be supported in pytest-django.

This works. From https://flowfx.de/blog/populate-your-django-test-database-with-pytest-fixtures/ :

import pytest

from django.core.management import call_command

@pytest.fixture(scope='session')
def django_db_setup(django_db_setup, django_db_blocker):
    with django_db_blocker.unblock():
        call_command('loaddata', 'potatoes_data.json')
# tests/test_models.py

def test_my_potatoes(db, django_db_setup):
    # GIVEN a full database of potatoes, as provided by the django_db_setup fixture
    all_my_potatoes = Potato.objects.all()

Current best practices do not advocate fixture loading from files, so it will not be supported in pytest-django.

The options for this, AFAIU, include:

  • Loading fixtures as JSON and/or YAML as needed

    • Natively supported by the framework

  • Generating Python source code
  • Trying to modify SQL dumps when migrations affect the underlying schema

    • Unnecessarily time consuming (for the developer)

    • A bit faster than JSON/YAML fixtures (on each test run)

For a large number of records, fixtures seem like a perfectly acceptable way to load test data into the database in Django. AFAIU, there is no plan to deprecate Django database fixtures due either to disuse or any best practices recommendations.

I think when we talk about best practices, we have to have the context of the practice.

On a staging site, it is possible that the fixtures described are previously installed from earlier generations. Whereas, sometimes this may not be the case when developing locally, or if you want to test a database from scratch.

The issue this presents is when an app with lots of different apps, which have custom calculations on the values stored in the database in order to present to a user.

In this use case, there may be data in the backend which drives these calculations, and whilst I completely agree, if I only need a user, then only create a user and that's all. Why do I need the back-end calculation in this case? Well, you don't. However, in the cases where one is creating tests that ensure the back-end calculations are correct, I would have to install each item I needed as a fixture, whereas Django would install them had they been placed in a class as is expected with the django test suite. Consider that there are use-cases for your repo that are beyond your personal projects :)

Best practice should be applied broadly and in a general fashion. I think being wary and cautious of over-applying the philosophy, else lose some of the automation conveniences that writing code satisfies.

Yeah there could be TestCase base/mixing classes with e.g. fixtureset class attrs that default to None or list config constants like ALL, or e.g. USER.

class TestCase:
    fixtures = None
    fixtures = ALL
    fixtures = USER
    fixtures = [,]
fixtures = ['book', 'author', 'etc...']
@pytest.fixture(autouse=True)
def django_fixtures_setup(django_db_setup, django_db_blocker):
    with django_db_blocker.unblock():
        call_command('loaddata', *['{}{}'.format(fixture, '.json') for fixture in fixtures])

Above works fine also though

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mpasternak picture mpasternak  路  5Comments

rlskoeser picture rlskoeser  路  7Comments

AndreaCrotti picture AndreaCrotti  路  5Comments

zsoldosp picture zsoldosp  路  5Comments

koxu1996 picture koxu1996  路  3Comments