Hers is a sample permission that I want to unit test.
# permissions.py
from myapp.models import Membership
class IsOrganizationOwner(permissions.BasePermission):
"""
Custom permission to allow only owner of the organization to do a certian task.
"""
def has_object_permission(self, request, view, obj):
try:
membership = Membership.objects.get(user = request.user, organization = obj)
except Membership.DoesNotExist:
return False
return membership.is_admin
and here is how it is applied
# viewsets.py
class OrganizationViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows Organizations to be viewed or edited.
"""
permission_classes = (permissions.IsAuthenticated, IsOrganizationOwner,)
queryset = Organization.objects.all().order_by('name')
serializer_class = OrganizationSerializer
Now I am very new to testing in django and I don't know how to test this permission. Any help would be appreciated.
DRF Permissions In DRF, permissions, along with authentication and throttling, are used to grant or deny access for different classes of users to different parts of an API. Authentication and authorization work hand in hand. Authentication is always executed before authorization.
Requests for unauthorised users will only be permitted if the request method is one of the "safe" methods; GET , HEAD or OPTIONS . This permission is suitable if you want to your API to allow read permissions to anonymous users, and only allow write permissions to authenticated users.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With