如何在django-rest-framework中单元测试权限?

时间:2016-05-11 18:42:57

标签: django unit-testing django-rest-framework

她是我想要进行单元测试的示例权限。

# 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

以及它的应用方式

# 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

现在我对django测试非常新,我不知道如何测试这个权限。任何帮助将不胜感激。

3 个答案:

答案 0 :(得分:10)

这是一种方法:

InputStream is = YourTestClassName.class.getClassLoader().getResourceAsStream("your/package/name/testFiles/config.properties")

我使用了library我写的模拟django queryset函数来使测试更小,更易读。但如果您愿意,可以使用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)) Mock仅修补您需要的内容。

编辑:为了完整起见,我们假设您还想进行集成测试MagicMock,这里有一些测试:

OrganizationViewSet

正如其他人所指出的那样,您也需要这些测试,而不是每个可能的测试用例。单元测试最适合这种情况,因为集成测试会写入数据库等,并且会使CI过程变慢 - 以防这类事情与您相关。

答案 1 :(得分:1)

我本人正在为此作斗争,我想我找到了一个简单的解决方案,可以单独测试权限检查的行为,而不必模拟所有内容。由于此答复是在原始答案后的四年后提出的,因此从那时起Django可能已经有了很大的发展。

测试权限似乎就像实例化权限并使用人为对象测试其has_permission方法一样容易。例如,我使用IsAdminUser权限对此进行了测试,然后测试通过了:

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)

在用户实例中将is_staff更改为False会导致测试失败,就像我期望的那样。

使用实际示例进行更新

我写了我自己的自定义权限,以检查用户是否是管理员(工作人员用户),否则只允许只读操作。注意,由于这是一个单元测试,因此它不与任何端点交互,甚至不试图模拟它们。它只是测试权限检查的预期行为。

这是许可:

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

这是完整的单元测试套件:

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)

我假设您可以对要为其写入权限的任何用户属性使用类似的模式。

在进行集成/功能测试时,只有这样,我才会担心此权限如何影响API的接口。

答案 2 :(得分:0)

作为上述方法的一个小增强/替代,我将使用pytest的admin_user夹具来提供django支持:

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

一个快乐的路径测试可能看起来像这样:

    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

您还可以测试用户未登录的相反情况:

   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