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/")