Want to Test APIs, Authentication and Permissions in Django? Here’s How

Master Django REST Framework, APIClient, APITestCase, and more for RESTful testing

Want to Test APIs, Authentication and Permissions in Django? Here’s How

Django is a popular web framework for building web applications with Python. It provides many features to make web development easier and faster, such as an ORM, a templating engine, a built-in admin interface, and more. One of the most important features of Django is its support for RESTful APIs, which allow you to expose your data and functionality to other applications or services.

However, building APIs with Django also requires testing them to ensure that they work as expected, that they are secure, and that they follow the best practices for API design. In my previous article, I showed you how to write tests for your Django apps effectively. In this article, I will show you how to test APIs, authentication and permissions in Django using some of the most common tools and libraries available.

Installing Django REST Framework

Before we can start testing APIs with Django, we will need to install Django REST Framework (DRF), a powerful and flexible toolkit for building RESTful APIs with Django. It provides many features to help you create and document your APIs, such as serializers, viewsets, routers, permissions, authentication, pagination, filtering, etc.

To install DRF, you can use pip or clone the project from GitHub.

pip install djangorestframework

You also need to add rest_framework to your INSTALLED_APPS setting in your settings.py file.

INSTALLED_APPS = [
    ...
    'rest_framework',
]

Testing APIs with Django REST Framework

One of the advantages of using DRF is that it also provides a built-in testing framework that extends Django's test client and makes it easier to test your API endpoints. You can use the APIClient or the APIRequestFactory classes to create requests and test the responses from your views. For example:

from rest_framework.test import APIClient
from django.test import TestCase

class TestAPI(TestCase):
    def setUp(self):
        self.client = APIClient()

    def test_get_posts(self):
        response = self.client.get('/api/posts/')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data), 10)

The APIClient class allows you to make requests using the same methods as the standard Django test client (get, post, put, patch, delete, etc.), but it also handles JSON encoding and decoding automatically. You can access the response data as a Python dictionary or list, and use the standard Python unittest assertions to check the status code, the content type, the headers, etc.

The APIRequestFactory class allows you to create requests without sending them to the server. You can then pass them to your view functions directly and test the response objects. This is useful if you want to test your views in isolation or if you want more control over the request parameters. For example:

from rest_framework.test import APIRequestFactory
from django.test import TestCase
from api.views import PostViewSet

class TestAPI(TestCase):
    def setUp(self):
        self.factory = APIRequestFactory()

    def test_get_posts(self):
        request = self.factory.get('/api/posts/')
        view = PostViewSet.as_view({'get': 'list'})
        response = view(request)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data), 10)

Testing Authentication and Permissions with DRF

One of the most important aspects of testing APIs is ensuring that they are secure and that they enforce the proper access control rules. DRF provides several options for authentication and permissions that you can use to protect your API endpoints from unauthorized or malicious users.

Authentication is the process of verifying the identity of a user who is requesting your API. DRF supports various authentication schemes, such as basic authentication, token authentication, session authentication, OAuth2, etc. You can specify which authentication classes you want to use for your API globally in your settings.py file or per view or viewset using the authentication_classes attribute.

Permissions are the rules that determine what actions a user can perform on your API resources. DRF provides several built-in permission classes, such as IsAuthenticated, IsAdminUser, IsOwnerOrReadOnly, etc. You can also create your custom permission classes by subclassing BasePermission and implementing the has_permission and has_object_permission methods. You can specify which permission classes you want to use for your API globally in your settings.py file or per view or viewset using the permission_classes attribute.

To test authentication and permissions with DRF, you need to simulate different scenarios where users with different credentials and roles try to access your API endpoints. You can use the force_authenticate method of the APIClient or the APIRequestFactory classes to set the user for a request.

from rest_framework.test import APIClient
from django.test import TestCase
from django.contrib.auth.models import User

class TestAPI(TestCase):
    def setUp(self):
        self.client = APIClient()
        self.user = User.objects.create_user(username='test', password='test')
        self.admin = User.objects.create_superuser(username='admin', password='admin')

    def test_get_posts_unauthenticated(self):
        response = self.client.get('/api/posts/')
        self.assertEqual(response.status_code, 401)

    def test_get_posts_authenticated(self):
        self.client.force_authenticate(user=self.user)
        response = self.client.get('/api/posts/')
        self.assertEqual(response.status_code, 200)

    def test_get_posts_admin(self):
        self.client.force_authenticate(user=self.admin)
        response = self.client.get('/api/posts/')
        self.assertEqual(response.status_code, 200)

    def test_post_posts_unauthenticated(self):
        response = self.client.post('/api/posts/', data={'title': 'New post', 'content': 'Hello world'})
        self.assertEqual(response.status_code, 401)

    def test_post_posts_authenticated(self):
        self.client.force_authenticate(user=self.user)
        response = self.client.post('/api/posts/', data={'title': 'New post', 'content': 'Hello world'})
        self.assertEqual(response.status_code, 201)

    def test_post_posts_admin(self):
        self.client.force_authenticate(user=self.admin)
        response = self.client.post('/api/posts/', data={'title': 'New post', 'content': 'Hello world'})
        self.assertEqual(response.status_code, 201)

To explain more about permissions in DRF, let's look at how the IsAuthenticated permission class works. This class checks if the request.user is authenticated or not and returns True or False accordingly. If the permission check fails, the view will return a 401 Unauthorized response. You can use this permission class to restrict access to your API endpoints to only logged-in users. For example:

from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ModelViewSet
from api.models import Post
from api.serializers import PostSerializer

class PostViewSet(ModelViewSet): 
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [IsAuthenticated]

This viewset will allow only authenticated users to perform CRUD operations on the Post model. If an unauthenticated user tries to access the endpoint, they will get a 401 response.

You can also combine different permission classes using logical operators, such as AND, OR, and NOT. For example, if you want to allow only the owner of a post or an admin user to edit or delete it, you can use the IsOwnerOrReadOnly and IsAdminUser permission classes together. For example:

from rest_framework.permissions import IsAuthenticated, IsAdminUser, IsOwnerOrReadOnly
from rest_framework.viewsets import ModelViewSet
from api.models import Post
from api.serializers import PostSerializer

class PostViewSet(ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [IsAuthenticated & (IsOwnerOrReadOnly | IsAdminUser)]

This viewset will allow authenticated users to list and create posts, but only the owner of a post or an admin user can update or delete it. If a user tries to perform an action that they are not allowed to do, they will get a 403 Forbidden response.

Conclusion

In this article, I have shown you how to test APIs, authentication and permissions in Django using Django REST Framework. Testing your APIs is essential to ensure that they work correctly, that they are secure, and that they follow the best practices for API design. DRF provides a convenient and powerful testing framework that makes it easy to create and test requests and responses from your views. You can also use different authentication and permission classes to protect your API endpoints from unauthorized or malicious users.

I hope you find it useful and informative!