[Django]-How to unit test permissions in django-rest-framework?

24👍

Here is one approach:

from django_mock_queries.query import MockSet
from mock import patch, MagicMock
from unittest import TestCase


class TestPermissions(TestCase):
    memberships = MockSet()
    patch_memberships = patch('myapp.models.Membership.objects', memberships)

    def setUp(self):
        self.permission = IsOrganizationOwner()

        self.memberships.clear()
        self.request = MagicMock(user=MagicMock())
        self.view = MagicMock()

    def create_membership(self, organization, is_admin):
        self.request.user.is_admin = is_admin
        self.memberships.add(
            MagicMock(user=self.request.user, organization=organization)
        )

    @patch_memberships
    def test_permissions_is_organization_owner_returns_false_when_membership_does_not_exist(self):
        org = MagicMock()
        self.assertFalse(self.permission.has_object_permission(self.request, self.view, org))

    @patch_memberships
    def test_permissions_is_organization_owner_returns_false_when_membership_is_not_admin(self):
        org = MagicMock()
        self.create_membership(org, False)
        self.assertFalse(self.permission.has_object_permission(self.request, self.view, org))

    @patch_memberships
    def test_permissions_is_organization_owner_returns_true_when_membership_is_admin(self):
        org = MagicMock()
        self.create_membership(org, True)
        self.assertTrue(self.permission.has_object_permission(self.request, self.view, org))

I used a library that I wrote that mocks django queryset functions to make the tests smaller and more readable. But if you prefer you can use Mock or MagicMock to patch only the things you need.

EDIT: For the sake of completeness let’s assume you also wanted to integration test OrganizationViewSet, here’s some tests for that:

from django.contrib.auth.models import User
from django.test import TestCase, Client
from model_mommy import mommy


class TestOrganizationViewSet(TestCase):
    url = '/organizations/'

    def create_user(self, is_admin):
        password = 'password'

        user = mommy.prepare(User, is_admin=is_admin)
        user.set_password(password)
        user.save()

        return user, password

    def get_organizations_as(self, user=None, password=None):
        api = Client()

        if user:
            mommy.make(Membership, user=user, organization=mommy.make(Organization))
            api.login(username=user.username, password=password)

        return api.get(self.url)

    def test_organizations_viewset_returns_200_for_admins(self):
        response = self.get_organizations_as(*self.create_user(True))
        self.assertEqual(response.status_code, 200)

    def test_organizations_viewset_returns_403_for_non_admins(self):
        response = self.get_organizations_as(*self.create_user(False))
        self.assertEqual(response.status_code, 403)

    def test_organizations_viewset_returns_403_for_anonymous(self):
        response = self.get_organizations_as()
        self.assertEqual(response.status_code, 403)

As others have pointed out, you need those tests too, just not for every possible test case. Unit tests are best for that, as integration tests will write to the database etc. and will make your CI procedures slower – in case that sort of thing is relevant to you.

👤fips

10👍

I was wrangling with this myself, and I think I found a simple solution that tests the behavior of the permissions check in isolation without having to mock everything out. Since this response is coming 4 years after the original answer, Django may have evolved considerably since then.

Testing a permission seems to be as easy as instantiating the permission and testing its has_permission method with contrived objects. For instance, I tested this out with the IsAdminUser permission, and the test passed:

from django.contrib.auth.models import User
from django.test import RequestFactory, TestCase
from rest_framework.permissions import IsAdminUser


class IsAdminUserTest(TestCase):
    def test_admin_user_returns_true(self):
        admin_user = User.objects.create(username='foo', is_staff=True)
        factory = RequestFactory()

        request = factory.delete('/')
        request.user = admin_user

        permission_check = IsAdminUser()

        permission = permission_check.has_permission(request, None)

        self.assertTrue(permission)

Changing is_staff to False in the User instantiation causes the test to fail as I’d expect.

Updating this with an actual example

I wrote my own custom permission to check if a user is an admin (staff user) and to allow only read-only operations otherwise. Note that, since this is a unit test, it doesn’t interface with any endpoint or even seek to mock those out; it just tests the expected behavior of the permissions check.

Here’s the permission:

from rest_framework import permissions


class IsAdminUserOrReadOnly(permissions.BasePermission):
    def has_permission(self, request, view):
        if request.method in permissions.SAFE_METHODS:
            return True

        return request.user.is_staff

And here’s the full unit test suite:

from django.contrib.auth.models import User
from django.test import RequestFactory, TestCase
from community.permissions import IsAdminUserOrReadOnly


class IsAdminOrReadOnlyTest(TestCase):
    def setUp(self):
        self.admin_user = User.objects.create(username='foo', is_staff=True)
        self.non_admin_user = User.objects.create(username='bar')
        self.factory = RequestFactory()

    def test_admin_user_returns_true(self):
        request = self.factory.delete('/')
        request.user = self.admin_user

        permission_check = IsAdminUserOrReadOnly()

        permission = permission_check.has_permission(request, None)

        self.assertTrue(permission)

    def test_admin_user_returns_true_on_safe_method(self):
        request = self.factory.get('/')
        request.user = self.admin_user

        permission_check = IsAdminUserOrReadOnly()

        permission = permission_check.has_permission(request, None)

        self.assertTrue(permission)

    def test_non_admin_user_returns_false(self):
        request = self.factory.delete('/')
        request.user = self.non_admin_user

        permission_check = IsAdminUserOrReadOnly()

        permission = permission_check.has_permission(request, None)

        self.assertFalse(permission)

    def test_non_admin_user_returns_true_on_safe_method(self):
        request = self.factory.get('/')
        request.user = self.non_admin_user

        permission_check = IsAdminUserOrReadOnly()

        permission = permission_check.has_permission(request, None)

        self.assertTrue(permission)

I assume you could use a similar pattern for just about any user attribute you wanted to write a permission against.

When I get to integration/functional testing, only then will I worry about how this permission affects the interfaces of the API.

👤Derek

1👍

As a small enhancement/alternative for the approach outlined above, I would use pytest’s admin_user fixture provided for django support:

https://pytest-django.readthedocs.io/en/latest/helpers.html#admin-user-an-admin-user-superuser

So a happy path test could look something like this:

    def test_returns_200_when_user_is_authenticated(self, 
         client, admin_user):
    client.force_login(admin_user)

    response = client.put('/my_url/pk/', data={'some data'},
        content_type='application/json'))

    assert response.status_code == 200

And you could also test the opposite scenario where the user is not logged in:

   def test_returns_403_when_user_is_not_authenticated(
        self, client):

    response = client.put('/my_url/pk/', data={'some data'},
        content_type='application/json'))

    assert response.status_code == 403

Leave a comment