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

Top comments (0)