【问题标题】:How to unit test permissions in django-rest-framework?如何在 django-rest-framework 中对权限进行单元测试?
【发布时间】:2016-09-07 08:14:14
【问题描述】:

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

# 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 中进行测试非常陌生,我不知道如何测试此权限。任何帮助将不胜感激。

【问题讨论】:

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


    【解决方案1】:

    这是一种方法:

    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))
    

    我使用了我编写的 library 来模拟 django 查询集函数,以使测试更小且更具可读性。但如果您愿意,可以使用MockMagicMock 只修补您需要的东西。

    编辑:为了完整起见,假设您还想进行集成测试OrganizationViewSet,以下是一些测试:

    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)
    

    正如其他人所指出的,您也需要这些测试,只是不是针对所有可能的测试用例。单元测试是最好的,因为集成测试会写入数据库等,并且会使你的 CI 过程变慢——以防这类事情与你有关。

    【讨论】:

    • 这看起来是一个非常不错的方法。如果这能解决我的问题,我会告诉你的。
    • 这确实测试了权限本身是否有效;通过使用 django 的测试客户端向端点发送请求以确保按预期返回 403 来测试权限是否正确应用于视图也很有用。
    • 是的,但 OP 要求提供unit test permissions 的方法,而不是集成测试OrganizationViewSet 的方法。尽管如此,我还是更新了我的答案以包括对此的测试。
    • 谢谢,它帮助了我。但是,我想改进某个地方,即在 MagicMock 上使用 MagicMock 方法创建成员资格是因为无法使用 Model.objects.filter 作为方面。相反,需要使用django_mock_queries.MockModel 类。
    【解决方案2】:

    我自己也在为此争论不休,我想我找到了一个简单的解决方案,可以单独测试权限检查的行为,而无需模拟所有内容。由于这个响应是在最初的答案之后 4 年才出现的,因此 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)
    

    在 User 实例化中将 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 的接口。

    【讨论】:

      【解决方案3】:

      作为上述方法的一个小增强/替代方案,我将使用 pytest 为 django 支持提供的 admin_user 固定装置:

      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
      

      【讨论】:

        猜你喜欢
        • 2014-12-07
        • 2015-01-27
        • 2021-07-30
        • 2018-05-14
        • 1970-01-01
        • 2021-04-25
        • 2015-06-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多