如何在Django rest框架中编写“删除”操作的测试

问题描述 投票:0回答:2

我正在为我的Django Rest Framework API编写测试。

我坚持测试'删除'。

我对'create'的测试运行正常。

这是我的测试代码:

import json

from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from users.models import CustomUser
from lists.models import List, Item

class ListAPITest(APITestCase):
    @classmethod

    def setUp(self):
        self.data = {'name': 'Test list', 'description':'A description', 'item': [
        {'name': 'Item 1 Name', 'description': 'Item 1 description', 'order': 1},
        {'name': 'Item 2 Name', 'description': 'Item 2 description', 'order': 2},
        {'name': 'Item 3 Name', 'description': 'Item 3 description', 'order': 3},
        {'name': 'Item 4 Name', 'description': 'Item 4 description', 'order': 4},
        {'name': 'Item 5 Name', 'description': 'Item 5 description', 'order': 5},
        {'name': 'Item 6 Name', 'description': 'Item 6 description', 'order': 6},
        {'name': 'Item 7 Name', 'description': 'Item 7 description', 'order': 7},
        {'name': 'Item 8 Name', 'description': 'Item 8 description', 'order': 8},
        {'name': 'Item 9 Name', 'description': 'Item 9 description', 'order': 9},
        {'name': 'Item 10 Name', 'description': 'Item 10 description', 'order': 10}
        ]}
        # 'lists' is the app_name set in endpoints.py
        # 'Lists' is the base_name set for the list route in endpoints.py
        # '-list' seems to be something baked into the api
        self.url = reverse('lists:Lists-list')

    def test_create_list_authenticated(self):
        """
        Ensure we can create a new list object.
        """

        user = CustomUser.objects.create(email='[email protected]', username='Test user', email_verified=True)

        self.client.force_authenticate(user=user)
        response = self.client.post(self.url, self.data, format='json')
        list_id = json.loads(response.content)['id']

        # the request should succeed
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

        # there should now be 1 List in the database
        self.assertEqual(List.objects.count(), 1)

    def test_delete_list_by_owner(self):
        """
        delete list should succeed if user created list
        """
        user = CustomUser.objects.create(email='[email protected]', username='Test user', email_verified=True)
        new_list = List.objects.create(name='Test list', description='A description', created_by=user, created_by_username=user.username)
        self.client.force_authenticate(user=user)
        response = self.client.delete(self.url + '/' + str(new_list.id))
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

而不是预期的状态204,我看到:

AssertionError: 405 != 204

405是不允许的方法。

这是我的模型定义:

class List(models.Model):
    """Models for lists
    """
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created_by = models.ForeignKey(USER, on_delete=models.CASCADE, related_name='list_created_by_id')
    created_by_username = models.CharField(max_length=255) # this shold be OK given that the list will be deleted if the created_by_id user is deleted
    created_at = models.DateTimeField(auto_now_add=True)
    parent_item = models.ForeignKey('Item', on_delete=models.SET_NULL, null=True, related_name='parent_item')
    modified_by = models.ForeignKey(USER, on_delete=models.SET_NULL, null=True,
        related_name='list_modified_by')
    modified_at = models.DateTimeField(auto_now_add=True)
    name = models.CharField(max_length=255)
    description = models.CharField(max_length=5000, blank=True, default='')
    is_public = models.BooleanField(default=False)

    def __str__(self):
        return self.name

这是我的观点:

class ListViewSet(FlexFieldsModelViewSet):
    """
    ViewSet for lists.
    """
    permission_classes = [IsOwnerOrReadOnly, HasVerifiedEmail]
    model = List
    serializer_class = ListSerializer
    permit_list_expands = ['item']
    pagination_class = LimitOffsetPagination

    def get_queryset(self):
        # unauthenticated user can only view public lists
        queryset = List.objects.filter(is_public=True)

        # authenticated user can view public lists and lists the user created
        # listset in query parameters can be additional filter
        if self.request.user.is_authenticated:
            listset = self.request.query_params.get('listset', None)

            if listset == 'my-lists':
                queryset = List.objects.filter(created_by=self.request.user)

            elif listset == 'public-lists':
                queryset = List.objects.filter(is_public=True)

            else:
                queryset = List.objects.filter(
                    Q(created_by=self.request.user) | 
                    Q(is_public=True)
                )

        # allow filter by URL parameter created_by
        created_by = self.request.query_params.get('created_by', None)

        if created_by is not None:
            queryset = queryset.filter(created_by=created_by)

        # return only lists that have no parent item
        toplevel = self.request.query_params.get('toplevel')
        if toplevel is not None:
            queryset = queryset.filter(parent_item=None)

        return queryset.order_by('name')

我已经阅读了docs但我无法找到如何设置删除请求。

我也试过这个:

kwargs = {'pk': new_list.id}
response = self.client.delete(self.url, **kwargs)

这给了我一个错误:

AssertionError: Expected view ListViewSet to be called with a URL keyword argument named "pk". Fix your URL conf, or set the `.lookup_field` attribute on the view correctly.

通过我的React前端的API,我的应用程序中的删除工作正常。

我知道我的对象被称为列表令人困惑......但是很难想到另一个名字,因为它就是它!

感谢您提出我在这里缺少的任何想法!

python api testing django-rest-framework http-delete
2个回答
0
投票

问题可能在于您如何制定URL。您可以通过执行以下操作直接撤消删除URL:

 url = reverse('lists:Lists-detail', kwargs={'pk': new_list.pk})
 self.client.delete(url). 

使用这种方法,您不会遇到遗忘斜杠或在不需要时添加斜杠等问题。问题也可能出现在您的视图中,因为您使用的是自定义ModelViewset,但您说它可以与JS客户端一起使用,因此可能不是问题所在。


1
投票

我建议您查看Django-restframework测试文档。

https://www.django-rest-framework.org/api-guide/testing/

这是我如何为您当前的情况编写测试的示例。

from rest_framework.test import APIRequestFactory, force_authenticate
from django.test import TestCase

class TestsAPIListDetailView(TestCase):

    def setUp(self):
        self.factory = APIRequestFactory()
        # This only matters if you are passing url query params e.g. ?foo=bar
        self.baseUrl = "/list/"

    def test_delete_with_standard_permission(self):

        # Creates mock objects
        user = CustomUser.objects.create(email='[email protected]', username='Test user', email_verified=True)
        new_list = List.objects.create(name='Test list', description='A description', created_by=user,
                                       created_by_username=user.username)

        # Creates a mock delete request.
        # The url isn't strictly needed here. Unless you are using query params e.g. ?q=bar
        req = self.factory.delete("{}{}/?q=bar".format(self.baseUrl, new_list.pk))

        current_list_amount = List.object.count()

        # Authenticates the user with the request object.
        force_authenticate(req, user=user)

        # Returns the response data if you ran the view with request(e.g if you called a delete request).
        # Also you can put your url kwargs(For example for /lists/<pk>/) like pk or slug in here. Theses kwargs will be automatically passed to view. 

        resp = APIListDetailView.as_view()(req, pk=new_list.pk)

        # Asserts.
        self.assertEqual(204, resp.status_code, "Should delete the list from database.")
        self.assertEqual(current_list_amount, List.objects.count() - 1, "Should have delete a list from the database.")

如果您不熟悉测试,可能值得看看工厂男孩嘲笑您的Django模型。 https://factoryboy.readthedocs.io/en/latest/

顺便说一下,你应该真的避免使用像“List”这样的通用词来表示你的模型名称。

© www.soinside.com 2019 - 2024. All rights reserved.