首页 > 解决方案 > 单元测试序列化程序 Django Rest Framework

问题描述

我是 django 的新手,我想测试我的 CRUD 以获得我所做的 Approval 类,但我遇到了错误:

回溯(最后一次调用):文件“E:\Django_Workspace\berryenroll\BerryEnroll\api\tests.py”,第 19 行,在 test_add serializer.create(validated_data) 文件“E:\Django_Workspace\berryenroll\BerryEnroll\api\ serializers.py",第 29 行,在 create valid_data['username'] = self.context['request'].user.username KeyError: 'request'

这是测试类:

class ApprovalTest(TestCase):
    def test_add(self):

        validated_data = dict()
        validated_data['status'] = 'APPROVED'
        validated_data['given_role'] = ['ROLE_ADMIN']
        validated_data['asked_role'] = ['ROLE_ADMIN']
        serializer = ApprovalRequestSerializer(data=validated_data)
        if serializer.is_valid():
            print(serializer.data)
            serializer.create(validated_data)
        else:
            print(serializer.errors)

这是带有 customUser 的 Approval 模型:

matrix_roles = settings.MATRIX_RULES
allowed_roles = list(set([elem.role for elem in matrix_roles])) # Convert to a unique list


LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()])
STATUS_CHOICES = sorted([(item, item) for item in ["PENDING", "APPROVED", "REJECTED", "CANCELED"]])
ROLE_CHOICES = sorted([(item, item) for item in allowed_roles])

class ApprovalRequest(models.Model):
    # TODO: Add caller_id ?
    username = models.CharField(blank=True, default='', max_length=255)
    status = models.CharField(choices=STATUS_CHOICES, default='PENDING', max_length=100)
    creation_date = models.DateTimeField(auto_now_add=True)
    created_by = models.CharField(max_length=255, blank=True, default='username')
    last_update = models.DateTimeField(null=True)
    updated_by = models.CharField(max_length=255, blank=True, null=True)
    asked_role = MultiSelectField(choices=ROLE_CHOICES, default=settings.ASKED_DEFAULT_ROLE, max_length=100)
    given_role = MultiSelectField(choices=ROLE_CHOICES, default=settings.ASKED_DEFAULT_ROLE, max_length=100)

    # given_role = models.CharField(choices=ROLE_CHOICES, default=settings.ASKED_DEFAULT_ROLE, max_length=100)

    class Meta:
        ordering = ['id']






class CustomUser(AbstractBaseUser, PermissionsMixin):
    username = models.CharField(_('username'), unique=True, max_length=255)

    roles = []
    objects = None

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = []

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')

    def save (self, **kwargs):
        """saving to DB disabled"""
        pass


    def get_group_permissions (self):
        """If you don't make your own permissions module,
           the default also will use the DB. Throw it away"""
        return [] # likewise with the other permission defs

    def get_and_delete_messages (self):
        """Messages are stored in the DB. Darn!"""
        return []

这是 ApprovalSerializer:

class ApprovalRequestSerializer(serializers.ModelSerializer):
    given_role = fields.MultipleChoiceField(choices=ROLE_CHOICES,required=False)
    asked_role = fields.MultipleChoiceField(choices=ROLE_CHOICES,required=False)

    class Meta:
        fields = '__all__'
        model = ApprovalRequest
        read_only_fields = ['username', 'creation_date', 'created_by', 'last_update', 'updated_by']


    def create(self, validated_data):
        now = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")

        """Override default dataform read-only fields"""
        validated_data['username'] = self.context['request'].user.username
        validated_data['status'] = 'PENDING'
        validated_data['created_by'] = self.context['request'].user.username
        validated_data['creation_date'] = now
        validated_data['updated_by'] = self.context['request'].user.username
        validated_data['last_update'] = now
        return super(ApprovalRequestSerializer, self).create(validated_data)

    def update(self, instance, validated_data):
        now = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
        validated_data['updated_by'] = self.context['request'].user.username
        validated_data['last_update'] = now

        res = super(ApprovalRequestSerializer, self).update(instance, validated_data)

        # Check if the new role is approved, the call the Gateway to update the user role
        if 'status' in validated_data.keys():
            if validated_data['status'] == 'APPROVED':
                logger.info("The role should be updated to %s" % list(validated_data['given_role']))
                server_url = settings.BCS_URL
                username = settings.BCS_ADMIN_USERNAME
                password = settings.BCS_ADMIN_PASSWORD
                bcs = System(server_url)
                bcs.login_as_admin(username, password)
                users = bcs.users()
                # Look for my user in the gateway and update it's 'authorities'
                for user in users:
                    if user['login'] == instance.username:
                        user['authorities'] = list(validated_data['given_role'])
                        bcs.update_user(user)
                        # TODO: Log warning if the user does not exist / This case should never happen / Synchro error with gateway
                # pass
        return res

我只想测试 create 和 update 方法,但我不知道如何添加 context['request'] 来测试它

标签: pythondjangodjango-rest-framework

解决方案


您需要使用 DRF 的 RequestFactory,如下所示:

from rest_framework.test import APIRequestFactory

class ApprovalTest(TestCase):
    def test_add(self):
        # ...
        user = SomeUserFactory()
        request_factory = APIRequestFactory()
        request = request_factory.post('/')
        request.force_authenticate(user)
        serializer = ApprovalRequestSerializer(data=validated_data, context={"request": request})

请注意,在某些情况下,您传递给请求工厂的 URL 可能很重要。此外,您可能需要覆盖测试用例中使用的 URL - 我的意思是有时您有应用程序使用的主 urls.py 和与 API 对应的其他一些 urls.py(当您有两个为应用程序和 API 服务的不同工作人员)。您可以通过urls像这样设置测试用例的 classvar 来做到这一点:

from django.test import override_settings

@override_settings(ROOT_URLCONF='my_app.urls')
class ApprovalTest(TestCase):
    def test_add(self):
        # ....

但是,由于您的序列化程序依赖于请求和用户,您也可以考虑为视图本身编写测试。然后视图将在后台调用序列化程序,因此您将能够通过视图测试序列化程序,如下所示:

from rest_framework.test import APIClient

class MyViewTestCase(TestCase):
    def test_add(self):
        user = SomeUserFactory()
        client = APIClient()

        # you can also use force_authenticate here to indicate that the user has
        # already been logged in or actually test what happens when you don't do this
        # presumabely the view should return a 401 or something similar.

        client.force_authenticate(user)

        # url could either be a path, e.g. /some-url/

        # url = "/some-api-endpoint/"

        # or you could use reverse, like so:

        # url = reverse('my-view-name', kwargs={ ... })

        response = client.post(url, data={"given_role": "something", "asked_role": "something-else"})

        # now you can check either the response code, like so:

        self.assertEqual(response.status_code, 200)

        # or you can check the content returned like so:

        self.assertEqual(response.json(), {"some": "expected", "json": "output"})

我希望这对你有所帮助。我也强烈建议你看看 pytest 和 pytest-django。感谢 pytest 夹具支持,您可以非常快速地摆脱代码复制。然后,您也可以用语句替换self.assert*()调用,assert foo == bar尽管这当然是个人偏好,与您的问题的本质无关。


推荐阅读