如何在 django-rest-framework 中对权限进行单元测试?
Posted
技术标签:
【中文标题】如何在 django-rest-framework 中对权限进行单元测试?【英文标题】:How to unit test permissions in 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 中进行测试非常陌生,我不知道如何测试此权限。任何帮助将不胜感激。
【问题讨论】:
【参考方案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 查询集函数,以使测试更小且更具可读性。但如果您愿意,可以使用Mock
或MagicMock
只修补您需要的东西。
编辑:为了完整起见,假设您还想进行集成测试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
【讨论】:
以上是关于如何在 django-rest-framework 中对权限进行单元测试?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 TemplateHTMLRenderer 在 Django-REST-Framework 中创建/放置?
如何使 DRF ( Django-REST-Framework) 令牌保持不变,使其在每次页面刷新后不会丢失?
django-rest-framework:如何序列化已经包含 JSON 的字段?
如何在 React 中显示来自 django-rest-framework 的错误消息