繁体   English   中英

在测试中使用 DRF APIClient 解析来自 DRF 视图集的 POST 请求的断言错误

[英]Assertion error parsing response to POST request from DRF viewset with DRF APIClient in tests

我在使用 Django Rest 框架测试引擎时遇到了一个奇怪的问题。 奇怪的是,以前在 Django 3 上一切正常,而在我迁移到 Django 4 后出现了这个问题。除了测试之外,一切正常,并按预期响应查询。

问题

我正在使用 DRF APIClient 对单元测试进行查询。 虽然 GET 请求可以按预期执行,但我无法使 POST 请求正常工作。

这是我为解决问题而创建的一些简约示例代码。 我正在使用的版本:

Python 3.9
Django==4.0.3
djangorestframework==3.13.1
from django.db import models
from django.urls import include, path
from django.utils import timezone
from rest_framework import routers, serializers, viewsets

router = routers.DefaultRouter()


# models.py

class SomeThing(models.Model):
    created_at = models.DateTimeField(default=timezone.now)
    title = models.CharField(max_length=100, null=True, blank=True)


# serializers.py

class SomeThingSerializer(serializers.ModelSerializer):
    class Meta:
        fields = "__all__"
        model = SomeThing


# views.py

class SomeThingViewSet(viewsets.ModelViewSet):
    queryset = SomeThing.objects.all().order_by('id')
    serializer_class = SomeThingSerializer


# urls.py

router.register("some-things", SomeThingViewSet, basename="some_thing")

app_name = 'question'
urlpatterns = (
    path('', include(router.urls)),
)

这是我的测试用例:

import json

from django.contrib.auth import get_user_model
from rest_framework import status
from rest_framework.test import APITestCase, APIClient


class TestUserView(APITestCase):
        self.some_user = get_user_model().objects.create(login="some_user@test.ru")

    @staticmethod
    def get_client(user):
        client = APIClient()
        client.force_authenticate(user=user)
        return client


    def test_do_something(self):
        client = self.get_client(self.compliance_chief)
        url = reverse('question:some_things-list')
        resp = client.post(
            path=url,
            data=json.dumps({"title": "Created Something"}),
            content_type="application/json",
        )
        assert resp.status_code == status.HTTP_201_OK

(是的,我必须使用一些身份验证来访问数据,但我认为这与问题无关。)我收到了一个冗长的回溯,以断言错误结束:

  File "/****/****/****/venv/lib/python3.9/site-packages/django/test/client.py", line 82, in read
    assert (
AssertionError: Cannot read more than the available bytes from the HTTP incoming data.

由于它真的很长,我会留下它以防万一,而不在这里发布。

修复步骤

在视图集返回正确的响应后,问题显然发生了。 为了确保响应正确,我在 create 方法中做了一些定制,以便在返回之前打印出响应,如下所示:

class SomeThingViewSet(viewsets.ModelViewSet):
    queryset = SomeThing.objects.all().order_by('id')
    serializer_class = SomeThingSerializer

    def create(self, request, *args, **kwargs):
        response = super().create(request, *args, **kwargs)
        print("THIS IS THE RESPONSE FROM THE VIEWSET", response)
        return response

而且,果然,结果是正确的:

THIS IS THE RESPONSE FROM THE VIEWSET <Response status_code=201, "text/html; charset=utf-8">

这让我觉得在解析阶段出了点问题(实际上,回溯意味着相同)。 我试图调整构建查询的方式,即:

  • 使用格式而不是内容类型,如下所示: resp = client.post(path=url, data={"title": "Created Something"}, format="json")
  • 使用 .generic 方法而不是 .post 像这样: resp = client.generic(method="POST", path=url, data=json.dumps({"title": "Created Something"}), content_type="application/json")

结果是一样的。

通过谷歌搜索,我发现这个错误确实偶尔发生在与 DRF APIClient 和 Django 相关的情况下,但很久以前(就像这个讨论,它声称该问题已在更高版本的 Django 中得到修复)。

我确信这种行为的原因是相当明显的(最有可能是一些愚蠢的错误)并且解决方案必须非常简单,但到目前为止我还没有找到它。 如果有人分享他们处理此类问题的经验(如果有的话),或者他们对如何摆脱这种僵局的考虑,我将不胜感激。

好吧,这个谜已经解决了,我将在这里分享它,以防有人遇到类似的事情,虽然这需要相当巧合,所以不太可能。

长话短说:我弄乱了我的 Django4.0.3 的源代码。 安装在这个项目中。

现在,它是如何发生的。 当我测试一些东西时,我遇到了一个错误,我没有找到,所以我沿着整个事件链检查 output 是否符合我的预期。 很快,我发现自己从安装在我的虚拟环境下的库中的函数中检查了 output。 我意识到直接修改他们的代码是一种弊端,但是当我在本地环境中工作时可以选择随时重新安装所有内容时,我认为可以与他们一起玩。 由于没有任何结果,我删除了我添加的所有代码(或者我认为是这样)。

过了一会儿,我意识到是什么导致了初始错误(我的测试设置中被忽略的情况),修复了它并尝试运行测试。 这时候问题就出现了。

后来我发现同样的测试在相同的环境中也能正确执行。 然后我怀疑我破坏了本地库代码中的某些内容。 接下来,我只是将我在本地环境中处理的代码与官方来源的代码进行了比较,很快我就建立了违规行。 它恰好在 django/test/client.py 中,在RequestFactory.generic方法的定义中。 像这样的东西:

...
        if not r.get("QUERY_STRING"):
            # WSGI requires latin-1 encoded strings. See get_path_info().
            query_string = parsed[4].encode().decode("iso-8859-1")
            r["QUERY_STRING"] = query_string
        req = self.request(**r)
        return self.request(**r)
...

违规行(我添加并忘记删除)是req = self.request(**r) 删除后,一切恢复正常。

暂无
暂无

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

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