简体   繁体   中英

Testing Django w/ DRF: issues defining a base test class for multiple test cases to inherit

I'm working on a Django app that exposes an API with the help of Django REST Framework (DRF). I've gotten to the point where I need to create a testing framework and have been poring over both the DRF and Django testing docs.

I've defined a BaseTestCase class that sets up basic required data needed for all other test cases, as well as a ModelTestCase class that inherits from BaseTestCase , in order to make use of the setup performed. Here's how those look right now:

BaseTestCase

class BaseTestCase(APITestCase):
  '''
  This class does basic data setup required to test API endpoints
  Creates 1+ users, sets the client to use that user for auth
  '''

  @classmethod
  def create_data(cls):
    '''
    Create the users needed by automated tests
    '''
    # creates some data used by child test cases            

  @classmethod
  def setUpClass(cls):
    client = APIClient()
    cls.client = client
    # call the method to create necessary base data
    cls.create_data()
    # get a user and set the client to use their auth
    user = get_user_model().objects.get(email='auto-test@test.com')
    client.force_authenticate(user=user)
    # cls.client = client
    super(BaseTestCase, cls).setUpClass()

  def test_base_data(self):
    '''
    This test ensures base data has been created
    '''
    # tests basic data to ensure it's created properly

  def test_get_users(self):
    '''
    This test attempts to get the list of users via the API
    It depends on the class setup being complete and correct
    '''
    url = '/api/users/'
    response = BaseTestCase.client.get(url, format='json')
    print(json.loads(response.content))
    self.assertEqual(response.status_code, status.HTTP_200_OK)

ModelTestCase

class ModelTestCase(BaseTestCase):
  @classmethod
  def setUpClass(cls):
    super(ModelTestCase, cls).setUpClass()
    client = APIClient()
    user = get_user_model().objects.all()[0]
    client.force_authenticate(user=user)
    cls.client = client

  def test_create_model(self):
    '''
    Make sure we can create a new model with the API
    '''
    url = '/api/model/'
    # set the request payload
    response = ModelTestCase.client.post(url, data, format='json')
    self.assertEqual(response.status_code, status.HTTP_201_CREATED)

When I run all tests, I get a failure because one of the BaseTestCase data verification asserts (based on a count of how many objects are present) fails due to there being too many (since the BaseTestCase has been set up twice- once on it's own, once as part of the setUpClass of ModelTestCase

When I run only the ModelTestCase , I get the following error:

======================================================================
ERROR: test_get_users (app.tests.ModelTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/path/to/tests.py", line 125, in test_get_users
    response = BaseTestCase.client.get(url, format='json')
AttributeError: type object 'BaseTestCase' has no attribute 'client'

----------------------------------------------------------------------

Which I don't understand- shouldn't the setUpClass() of BaseTestCase be running as normal?

I've also tried defining the ModelTestCase to inherit from APITestCase - with this configuration, running all tests succeeds, but (I believe) only because tests are run alphabetically, so the BaseTestCase runs, sets up data, and then the ModelTestCase can make use of that data.

I'd like for the tests themselves to be independent- and I believe setUpData() can be used for that. However, I also want the client setup (for auth) as well as data setup (which will end up being relatively expensive, I think) to be shared across test cases so that it wouldn't need to be repeated per-case, which is why I thought creating a base class to inherit from was the way to go.

Is there a way to accomplish what I've outlined? Should I be using setUpData() instead of setUpClass() ? Or is there a way to create my BaseTestCase class and have it not run when tests are executed?

According to Django Rest Framework Testing section, you should keep the default schema using setUp() methods. That's how I've found a resonable solution:

from django.urls import reverse
from rest_framework.test import APITestCase
import pytest

from core.factories import UserFactory, ClientFactory, AdministratorFactory, FacilitatorFactory, ParticipantFactory 


class BaseAPITestCase(APITestCase):
    def setUp(self):
        self.user = UserFactory()
        self.client.force_authenticate(user=self.user)


class UserTestCase(BaseAPITestCase):
    def setUp(self):
        # Call Parent's constructor.
        super(self.__class__, self).setUp()
        self.clients = ClientFactory(user=self.user)
        self.administrator = AdministratorFactory(user=self.user)
        self.facilitator = FacilitatorFactory(user=self.user)
        self.participant = ParticipantFactory(user=self.user)

    def test_get_users(self):
        url = reverse("api_v1:user-list")
        response = self.client.get(url)
        self.assertEquals(response.data["count"], 1)
        self.assertEquals(response.data["results"][0]["username"], self.user.username)
        self.assertEquals(response.status_code, 200)

Result:

============================================================================== test session starts ===============================================================================
platform linux -- Python 3.7.6, pytest-5.3.0, py-1.8.1, pluggy-0.13.1 -- 
cachedir: .pytest_cache
Django settings: tests.settings (from ini file)
Using --randomly-seed=1589794116
plugins: django-3.7.0, django-test-migrations-0.1.0, randomly-3.1.0, hypothesis-4.53.0, timeout-1.3.0, testmon-1.0.0, cov-2.8.0, deadfixtures-2.1.0
collected 1 items                                                                                                                                                               

tests/api/test_basics.py::UserTestCase::test_get_users PASSED

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM