DEV Community

Cover image for Testing with Django REST Framework
Jean-Michel Plourde
Jean-Michel Plourde

Posted on

Testing with Django REST Framework

It’s not because things are difficult that we dare not venture. It’s because we dare not venture that they are difficult. - Seneca

A section of Django REST framework (DRF) documentation is dedicated to testing your API views. It has a lot of information about the different tools the library is providing and code examples on how to use them. I was asked how to test an API call by mimicking a request, so here is how I usually do it in my projects.

The serializer

First, we have a basic model for a home which fields describe its address. BaseModel is an abstract class that adds a UUID, a created_at and a modified_at fields to my objects:



class Home(BaseModel):
    class SupportedCountries:
        CAN = "CANADA"
        US = "UNITED_STATES"

        CHOICES = (
            (CAN, _("Canada")),
            (US, _("United States")),
        )

    name = models.CharField(max_length=50, default="Home")
    address_line_1 = models.CharField(max_length=500)
    address_line_2 = models.CharField(
        max_length=500, blank=True, verbose_name=_("Address line 2 (optional)")
    )
    city = models.CharField(max_length=100)
    state_province = models.CharField(max_length=50, verbose_name=_("State/Province"))
    zip_code_postal_code = models.CharField(
        max_length=7, verbose_name=_("Zip code/Postal code")
    )
    country = models.CharField(
        max_length=15,
        choices=SupportedCountries.CHOICES,
        default=SupportedCountries.US,
    )

    def __str__(self):
        return self.name


Enter fullscreen mode Exit fullscreen mode

The resulting serializer is thus very simple with no inner serializer. We declare all the fields from the model plus the id field from the BaseModel abstract class:



class HomeSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Home
        fields = [
            "id",
            "name",
            "address_line_1",
            "address_line_2",
            "city",
            "state_province",
            "zip_code_postal_code",
            "country",
        ]


Enter fullscreen mode Exit fullscreen mode

The test class

To generate objects based on my models for my tests, I use factory boy and I populate the fields with fake data using faker:



import factory
from factory.django import DjangoModelFactory


class HomeFactory(DjangoModelFactory):
    name = "Home"
    address_line_1 = factory.Faker("street_address")
    address_line_2 = factory.Faker("building_number")
    city = factory.Faker("city")
    state_province = factory.Faker("state")
    zip_code_postal_code = factory.Faker("postcode")
    country = models.Home.SupportedCountries.US

    class Meta:
        model = models.Home


Enter fullscreen mode Exit fullscreen mode

I usually use Django TestCase class for testing purposes, which is a subclass of Python unittest.TestCase. It has a client to authenticate a user so you could test mixins and permission behaviors. DRF has a class called APITestCase that extends Django TestCase with the same functions but using APIClient which allows one to authenticate as an API user.



class TestHomeAPIView(APITestCase):
    def setUp(self):
        self.url = reverse("api:home_view") # use the view url
        self.home = factories.HomeFactory()
        user = factories.UserFactory()
        self.client.force_authenticate(user=user)

    def test_get(self):
        response = self.client.get(self.url)
        response.render()
        self.assertEquals(200, response.status_code)
        expected_content = {
                "id": str(self.home.id),
                "address_line_1": self.home.address_line_1,
                "address_line_2": self.home.address_line_2,
                "city": str(self.home.city),
                "state_province": str(self.home.state_province),
                "zip_code_postal_code": str(self.home.zip_code_postal_code),
                "country": str(self.home.country),
            }
        self.assertListEqual(expected_content, json.loads(response.content))


Enter fullscreen mode Exit fullscreen mode

Let's dive into that test code and explain what it does. First we have the setUp function which is common to TestCase and define code that will be run before every function prefixed with test_ in the class. I define the url for my view and a home object that will be recreated each time, making my test deterministic. I then create a user with a factory that gives the permissions I need for testing the view. The last line is really important, even more so for an API view with authentication and permission mechanisms. By default, the DEFAULT_PERMISSION_CLASSES array contains rest_framework.permissions.IsAuthenticated so all my views require to be logged in as a user. We authenticate the user with the APIClient so we can make calls to the view and the permissions can be evaluated.

I then declare test_get to make a simple GET test of my view. I first capture the call response into a variable of the same name. At first, I assumed response.content would be available, but because of template rendering mechanism internal to Django, it is not. We need to manually declare the rendering. Then I assert that my response was successful and generated an HTTP 200 that means everything went fine. The next line declares an expected_content variable which is dict containing all the fields of my home object. Lastly, I parse a JSON object to a Python object using json.loads, which I compare to my expected content to make sure what I received is correct.

Wrapping up

Django REST Framework has a great documentation with a thorough section on tests. We went through some bits of code for the model, the serializer and finally the test. This test was written à la Django using factory boy and Faker, and leveraging DRF APIClient contained in APITestCase testing class.

We went from start to finish of my testing process for an API view. There might be some portions that could be optimized, but it proved to be reliable for me.

Please share with me any tips or amazing bits of code that make your testing experience with Django REST Framework easier. If you spot an error, do not hesitate to leave a comment.

Top comments (2)

Collapse
 
ori75660 profile image
Ori Roza

I liked your article!
Im inviting you to use my new package for rest framework called drf-api-action

which is designed to elevate your testing experience for (DRF) REST endpoints. this package empowers you to effortlessly test your REST endpoints as if they were conventional functions.

Features:

Simplified Testing: Testing DRF REST endpoints using the api-action decorator, treating them like regular functions.

Seamless Integration: Replacing DRF's action decorator with api-action in your WebViewSet seamlessly.

Clear Traceback: Instead of getting a response with error code, get the real traceback that led to the error.

Pagination Support: Paginating easily through pages by a single kwarg.

github.com/Ori-Roza/drf-api-action

thanks!

Collapse
 
jmplourde profile image
Jean-Michel Plourde

I stumbled on your project a couple of weeks ago. I will definitely try and see how I can leverage it in my projects. Thanks!