簡體   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