How to use pytest fixtures with django TestCase
Asked Answered
A

3

18

How can I use a pytest fixture within a TestCase method? Several answers to similar questions seem to imply that my example should work:

import pytest

from django.test import TestCase
from myapp.models import Category
  
pytestmark = pytest.mark.django_db

@pytest.fixture
def category():
    return Category.objects.create()
  
class MyappTests(TestCase):
    def test1(self, category):
        assert isinstance(category, Category)

But this always results in an error:

TypeError: test1() missing 1 required positional argument: 'category'

I realize I could just convert this trivial example into a function, and it would work. I would prefer to use django's TestCase because it includes support for importing traditional "django fixture" files, which several of my tests require. Converting my tests to functions would require re-implementing this logic, since there isn't a documented way of importing "django fixtures" with pytest (or pytest-django).

package versions:

Django==3.1.2
pytest==6.1.1
pytest-django==4.1.0
Appendectomy answered 25/10, 2020 at 21:24 Comment(0)
L
10

I find it easier to use the "usefixtures" approach. It doesn't show a magical 2nd argument to the function and it explicitly marks the class for having fixtures.

@pytest.mark.usefixtures("category")
class CategoryTest(TestCase):
    def test1(self):
        assert Category.objects.count() == 1
Laureen answered 26/10, 2020 at 2:55 Comment(2)
Thanks, but this wouldn't work in my case because the "pytest fixtures" shouldn't be applied to all the tests within the class. Also, I need to access to either the model being returned from the fixture, or its primary key. That being said, I found a workaround shown in the answer I posted.Appendectomy
Yeah, I figured they shouldn't be but it's functionally identical to what you've tried to do above. I prefer to not to mix pytest and TestCase as well and pick the best tool for the job at hand.Laureen
A
6

I opted to rewrite django's fixture logic using a "pytest fixture" that is applied at the session scope. All you need is a single fixture in a conftest.py file at the root of your test directory:

import pytest

from django.core.management import call_command

@pytest.fixture(scope='session')
def django_db_setup(django_db_setup, django_db_blocker):
    fixtures = [
        'myapp/channel',
        'myapp/country',
        ...
    ]

    with django_db_blocker.unblock():
        call_command('loaddata', *fixtures)

This allowed me to throw out the class-based tests altogether, and just use function-based tests.

docs

Appendectomy answered 27/10, 2020 at 17:4 Comment(0)
M
3

Why do you need TestCase? I usually use Python class and create tests there.

Example

import pytest
from django.urls import reverse
from rest_framework import status

from store.models import Book
from store.serializers import BooksSerializer


@pytest.fixture
def test_data():
    """Creates temporary data."""
    Book.objects.create(name='Book1', price=4000)


@pytest.fixture
def api_client():
    """Returns APIClient to create requests."""
    from rest_framework.test import APIClient
    return APIClient()


@pytest.mark.django_db
class TestBooks:
    @pytest.mark.usefixtures("test_data")
    def test_get_books_list(self, api_client):
        """GET request to the list of books."""
        url = reverse('book-list')
        excepted_data = BooksSerializer(Book.objects.all(), many=True).data
        response = api_client.get(url)
        assert response.status_code == status.HTTP_200_OK
        assert response.data == excepted_data
        assert response.data[0]['name'] == Book.objects.first().name
Managerial answered 16/12, 2021 at 10:54 Comment(1)
May not be necessary for a general django TestCase, but if you're using StaticLiveServerTestCase for example, you need to inherit to make use of the extra functionality.Shultz

© 2022 - 2024 — McMap. All rights reserved.