The initial situation: fixture loading using TestCase

Django model fixtures are probably one of the most convenient way to deal with complex data dependencies while testing your application.

Fixtures in a nutshell

django fixtures are simply initial data in a serialized format such as json or yaml written to populate your models and explicit the relations between business entities. In the following example, some client is attached to division BU

clients.yaml

- model: app.client
  pk: 1
  fields:
    name: some client
    division_fk: 1

divisions.yaml

- model: app.division
  pk: 1
  fields:
    name: division BU
    code_site: BU
- model: app.division
  pk: 2
  fields:
    name: some other division
    code_site: BA

Fixtures in testing

What I often saw on Django projects and applied myself several times involves sourcing test data alongside your application somewhere in test/fixtures,

your_app/
    project/ 
        settings.py
        ....
    app/
        models.py
        tests.py
        ....
test/
    fixtures/
        users.yaml
        clients.yaml
        retails.yaml
        divisions.yaml
        ....

and explicitly load the path in settings.py

FIXTURE_DIRS = ['./test/fixtures']

in order to easily populate your database before any test

class TestSomeApiRouting(TestCase):
    fixtures = ['users.yaml', 'clients.yaml', 'divisions.yaml']

    def setUp(self):
        self.client = Client(HTTP_USER_AGENT='Mozilla/5.0')

    def test_clients_are_linked_to_divisions(self):
        response = self.client.get("/api/clients/")
        ....

This testing pattern is great, really straightforward. Moreover, django.test.TestCase automagically loads fixtures in the right order based on data dependency. Unfortunately, unittest is sometimes a bit limited compared to pytest

Loading models’ fixtures in pytest

I won’t go into the details of how to migrate from unittest to pytest: pytest-django documentation is pretty complete on the subject. The rest of the article presupposes that you’ve already made the migration and that you are able to run your django tests using pytest your_app

What I found most frustrating after this migration was that I lost this easy way to populate my database before tests. Using pytest involves to write every database fixture. In other words, the work I made while writing my yaml files was lost. Here is a trick I found to load models fixtures files using pytest as easily as you would do it with django.test.TestCase

pytestmark = pytest.mark.django_db

@pytest.fixture(scope='session')
def django_db_setup(django_db_setup, django_db_blocker):
    # no matter the order....
    fixtures = ['users.yaml', 'clients.yaml', 'divisions.yaml']
    with django_db_blocker.unblock():
        for fixture in fixtures:
            with django_db_blocker.unblock():
                # ...this will fix dependencies order
                try:
                    call_command('loaddata', fixture)
                except IntegrityError:
                    fixtures.append(fixture)

@pytest.mark.django_db
class TestSomeApiRouting
    def test_clients_are_linked_to_divisions(self):
        client = Client(HTTP_USER_AGENT='Mozilla/5.0')
        response = client.get("/api/clients/")