简体   繁体   English

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

[英]How to write a test for 'delete' operation in Django rest framework

I'm writing tests for my Django Rest Framework API. 我正在为Django Rest Framework API编写测试。

I'm stuck on testing 'delete'. 我一直在测试“删除”。

My test for 'create' works fine. 我对“创建”的测试效果很好。

Here's my test code: 这是我的测试代码:

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='person@example.com', 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='person@example.com', 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)

Instead of the expected status 204, I'm seeing: 我看到的不是预期的状态204,而是:

AssertionError: 405 != 204

405 is method not allowed. 405是不允许的方法。

Here's my model definition: 这是我的模型定义:

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

Here's my viewset: 这是我的观点:

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

I have read the docs but I haven't been able to find how to set up the delete request. 我已经阅读了文档,但无法找到如何设置删除请求的方法。

I have also tried this: 我也尝试过这个:

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

This gives me an error: 这给我一个错误:

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.

Delete in my app works fine via the API in my React front end. 通过我的React前端中的API,在我的应用程序中进行删除可以正常工作。

I know it's confusing that my object is called List...but it's hard to think of another name because that's what it is! 我知道我的对象叫做List令人困惑,但是很难想到另一个名字,因为那是事实!

Thank you for for any ideas what I'm missing here! 感谢您的任何想法,我在这里缺少什么!

I recommend you have a look at the Django-restframework testing documentation. 我建议您看看Django-restframework测试文档。

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

This is an example of how i would write a test for your current situation. 这是我如何为您当前情况编写测试的示例。

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='person@example.com', 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.")

If you are new to testing it might be worth having a look at factory boy for mocking your Django models. 如果您不熟悉测试,可能值得看看工厂男孩来模拟您的Django模型。 https://factoryboy.readthedocs.io/en/latest/ https://factoryboy.readthedocs.io/en/latest/

By the way you should really avoid using generic words like "List" for your model names. 顺便说一句,您应该真正避免在模型名称中使用通用词,例如“ List”。

The issue may be on how you're formulating the URL. 问题可能出在您如何制定URL上。 You can reverse the URL for delete directly by doing this: 您可以通过以下操作直接撤消删除的URL:

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

With this approach, you won't have issues like forgetting a trailing slash or adding it when it's not needed. 使用这种方法,您将不会遇到忘记尾随斜杠或在不需要时添加斜杠的问题。 The issue could also be in your viewset since you're using a custom ModelViewset but you said it works with the JS client so it may not be the problem. 该问题也可能出现在您的视图集中,因为您使用的是自定义ModelViewset,但是您说它可以与JS客户端一起使用,因此可能不是问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM