django - Django Rest Framework APIClient 在测试期间未处理异常
问题描述
我正在测试一个应该在 Django 模型中引发 ValidationError 的 API 端点(请注意,该异常是 Django 异常,而不是 DRF,因为它在模型中)。
from rest_framework.test import APITestCase
class TestMyView(APITestCase):
# ...
def test_bad_request(self):
# ...
response = self.client.post(url, data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
但是,我的测试出现异常而不是通过。它甚至没有失败获得 500 而不是 400,它根本没有到达那里。DRF 的 APIClient 不应该处理所有异常吗?我在网上搜索过,但一无所获。我读过 DRF 不处理 Django 的本机 ValidationError,但这仍然不能解释为什么我什至没有得到 500。知道我做错了什么吗?
完整的堆栈跟踪:
E
======================================================================
ERROR: test_cannot_create_duplicate_email (organizations.api.tests.test_contacts.TestContactListCreateView)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/code/organizations/api/tests/test_contacts.py", line 98, in test_cannot_create_duplicate_email
response = self.jsonapi_post(self.url(new_partnership), data)
File "/code/config/tests/base.py", line 166, in jsonapi_post
url, data=json.dumps(data), content_type=content_type)
File "/usr/local/lib/python3.7/site-packages/rest_framework/test.py", line 300, in post
path, data=data, format=format, content_type=content_type, **extra)
File "/usr/local/lib/python3.7/site-packages/rest_framework/test.py", line 213, in post
return self.generic('POST', path, data, content_type, **extra)
File "/usr/local/lib/python3.7/site-packages/rest_framework/test.py", line 238, in generic
method, path, data, content_type, secure, **extra)
File "/usr/local/lib/python3.7/site-packages/django/test/client.py", line 422, in generic
return self.request(**r)
File "/usr/local/lib/python3.7/site-packages/rest_framework/test.py", line 289, in request
return super(APIClient, self).request(**kwargs)
File "/usr/local/lib/python3.7/site-packages/rest_framework/test.py", line 241, in request
request = super(APIRequestFactory, self).request(**kwargs)
File "/usr/local/lib/python3.7/site-packages/django/test/client.py", line 503, in request
raise exc_value
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python3.7/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/django/views/generic/base.py", line 71, in view
return self.dispatch(request, *args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/rest_framework/views.py", line 495, in dispatch
response = self.handle_exception(exc)
File "/usr/local/lib/python3.7/site-packages/rest_framework/views.py", line 455, in handle_exception
self.raise_uncaught_exception(exc)
File "/usr/local/lib/python3.7/site-packages/rest_framework/views.py", line 492, in dispatch
response = handler(request, *args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/rest_framework/generics.py", line 244, in post
return self.create(request, *args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/rest_framework/mixins.py", line 21, in create
self.perform_create(serializer)
File "/usr/local/lib/python3.7/site-packages/rest_framework/mixins.py", line 26, in perform_create
serializer.save()
File "/usr/local/lib/python3.7/site-packages/rest_framework/serializers.py", line 214, in save
self.instance = self.create(validated_data)
File "/code/organizations/api/serializers.py", line 441, in create
'partnership': self.context['partnership']
File "/usr/local/lib/python3.7/site-packages/rest_framework/serializers.py", line 943, in create
instance = ModelClass._default_manager.create(**validated_data)
File "/usr/local/lib/python3.7/site-packages/django/db/models/manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/django/db/models/query.py", line 422, in create
obj.save(force_insert=True, using=self.db)
File "/code/organizations/models.py", line 278, in save
self.full_clean()
File "/usr/local/lib/python3.7/site-packages/django/db/models/base.py", line 1203, in full_clean
raise ValidationError(errors)
django.core.exceptions.ValidationError: {'__all__': ['Supplier contact emails must be unique per organization.']}
解决方案
问题:DRF 的 APIClient 不应该处理所有异常吗?
答:不。它是一个测试客户端,它不会处理任何未捕获的异常,这就是测试客户端的工作方式。测试客户端传播异常,以便在未捕获异常时测试失败并“崩溃”。您可以测试是否引发了异常并且未被捕获self.assertRaises
问题:当我引发 ValidationError 但未捕获异常时,APIView 应该返回 HTTP_400_BAD_REQUEST。
答案:
您应该查看APIView 的源代码。
在方法内部,在创建对象时dispatch()
引发的所有异常都会被捕获并调用该方法。response
handle_exception()
您的例外是ValidationError
. 关键线路是:
exception_handler = self.get_exception_handler()
context = self.get_exception_handler_context()
response = exception_handler(exc, context)
if response is None:
self.raise_uncaught_exception(exc)
如果你没有改变settings.EXCEPTION_HANDLER
,你会得到默认的 DRF 异常处理程序,源代码在这里。
如果处理Http404
,PermissionDenied
和APIException
. APIView
本身实际上也处理AuthenticationFailed
and 。NotAuthenticated
但不是ValidationError
。所以它会返回None
,因此视图会引发您ValidationError
的测试,从而停止您的测试。
您在回溯中看到:
File "/usr/local/lib/python3.7/site-packages/rest_framework/views.py", line 455, in handle_exception
self.raise_uncaught_exception(exc)
您可以决定处理比 DRF 处理的默认异常更多的异常,您可以在自定义异常处理中阅读此内容。
编辑:您也可以raise rest_framework.exceptions.ValidationError
代替标准的 Django ValidationError
。这是一个APIException
,因此将由 DRF 作为HTTP400_BAD_REQUEST
. [1]
旁注:幸运的是,DRF 并没有捕捉到每一个异常!如果您的代码存在严重缺陷,您实际上希望您的代码“崩溃”并生成错误日志,并且您的服务器返回 HTTP 500。这就是这里发生的情况。如果这不是测试客户端,则响应将是 HTTP 500。
[1] https://github.com/encode/django-rest-framework/blob/3.9.0/rest_framework/exceptions.py#L142
推荐阅读
- flutter - 我可以将对象作为参数传递给小部件吗
- javascript - 当我使用 Redux 在 React js 中更改路由时,为什么捐赠者卡消失并且表单为空
- excel - 使用不同工作簿上的按钮生成报告,然后将数据复制到工作簿中
- c# - 转换 DataGrid 中的项
- php - 计算两个日期之间的月份以及哪些月份是(PHP)
- c# - AddOpenIdConnect 与手动配置 - SecurityTokenSignatureKeyNotFoundException
- c# - 查询 XML 文件的更简单方法?C# - MVC .Net
- java - 在受保护的 void 超级方法中返回
- java - 使用包含多个 JSON 数组的 Java 解析来自 api 的 JSON 响应
- c# - 连接到 SQL Server 时登录失败错误