DEV Community

Cover image for Simplifying django-rest-framework Testing with drf-api-action
Ori Roza
Ori Roza

Posted on

Simplifying django-rest-framework Testing with drf-api-action


Testing Django Rest Framework #DRF endpoints can sometimes be a complex and time-consuming process. However, the #drf-api-action Python package aims to simplify and enhance the testing experience by introducing a custom decorator, api-action.
In this article, we will explore how to leverage #drf-api-action to streamline testing in DRF, making the development process more time-efficient and enjoyable.


Installation:

Let's start by installing the #drf-api-action package using pip:

pip install drf-api-action
Enter fullscreen mode Exit fullscreen mode

Usage:

Now that we have the package installed, let's dive into how
#drf-api-action can significantly improve testing workflows in #DRF.
Add the package to an existing project:
we have a User model which exposes 2 endpoints:

  • /api/users/{id} : A GET request that returns details on a specific user.
  • /api/users/ : A POST request that creates a new user.
from rest_framework import mixins, viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response

from drf_api_action_example.users.models import User
from drf_api_action_example.users import serializers

class UsersViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):

    @action(detail=True,
            methods=['get'],
            url_path='/',
            url_name='users/',
            serializer_class=serializers.GetUserDetailsSerializer)
    def get_user_details(self, request, **kwargs):
        """
        returns user details, pk expected
        """
        serializer = self.get_serializer(instance=self.get_object())
        return Response(data=serializer.data, status=status.HTTP_200_OK)

    @action(detail=False,
            methods=['post'],
            url_path='/',
            url_name='users/',
            serializer_class=serializers.AddUserSerializer)
    def add_user(self, request, **kwargs):
        """
        adds a new user
        """
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(data=serializer.data, status=status.HTTP_201_CREATED)
Enter fullscreen mode Exit fullscreen mode

Now, to apply the benefits of #drf-api-action we will change the following:

from rest_framework import mixins, viewsets, status
#  instead of from rest_framework.decorators import action do:
from drf_api_action.mixins import APIRestMixin
from drf_api_action.decorators import action_api
from rest_framework.response import Response

from drf_api_action_example.users.models import User
from drf_api_action_example.users import serializers

#  inheriting APIRestMixin
class UsersViewSet(APIRestMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):

    #  replacing @action decorator
    @action_api(detail=True,
                methods=['get'],
                url_path='/',
                url_name='users/',
                serializer_class=serializers.GetUserDetailsSerializer)
    def get_user_details(self, request, **kwargs):
        """
        returns user details, pk expected
        """
        serializer = self.get_serializer(instance=self.get_object())
        return Response(data=serializer.data, status=status.HTTP_200_OK)

    #  replacing @action decorator
    @action_api(detail=False,
                methods=['post'],
                url_path='/',
                url_name='users/',
                serializer_class=serializers.AddUserSerializer)
    def add_user(self, request, **kwargs):
        """
        adds a new user
        """
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(data=serializer.data, status=status.HTTP_201_CREATED)
Enter fullscreen mode Exit fullscreen mode

Fun Part - Writing tests!

First, import:

import pytest
from users.models import User
from users.views import UsersViewSet
from rest_framework.exceptions import ValidationError
Enter fullscreen mode Exit fullscreen mode

The first test test_get_user_detais simply tests /api/users/{id} GET endpoint:

import pytest
from users.models import User
from users.views import UsersViewSet
from rest_framework.exceptions import ValidationError

users_api = UsersViewSet()

def test_get_user_details(users_api):
    user = User(first_name='shosho', last_name='bobo', age=30)
    user.save()

    user_details = users_api.get_user_details(pk=user.id)
    assert user_details['first_name'] == user.first_name
Enter fullscreen mode Exit fullscreen mode
  1. The second test test_add_user simply tests /api/users POST endpoint:
def test_add_user(users_api):
    output = users_api.add_user(first_name='bar', last_name='baz', age=30)
    assert output['id'] is not None
Enter fullscreen mode Exit fullscreen mode
  1. The third test test_add_user_exception_on_age simply tests /api/users POST endpoint when age is not in the valid range:
def test_add_user_exception_on_age(users_api):
    with pytest.raises(ValidationError) as error:
        users_api.add_user(first_name='bar', last_name='baz', age=150)
    assert "Ensure this value is less than or equal to 120" in str(error.value)
Enter fullscreen mode Exit fullscreen mode

Unlike traditional #testing methods, #drf-api-action provides clear tracebacks, making it easier to identify and fix errors in your code.


Demo


Conclusion

The #drf-api-action package is a game-changer for testing #DRF/#Django endpoints in #Python. By simplifying the testing process and providing clear tracebacks and pagination support, it allows developers to focus on writing robust and time-efficient code. 
With #drf-api-action, testing in #DRF becomes more intuitive and enjoyable, contributing to an overall improved development experience.
Start leveraging this powerful package in your #DRF projects today!

P.S if you liked it, star it on #GitHub 💫


Resources

full example project
drf-api-action source code
django-rest-framework documentation

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more