[英]How to mock view decorator in Django
Consider that I have a simple APIView as below,考虑到我有一个简单的 APIView 如下,
from rest_framework.views import APIView
from rest_framework.response import Response
def my_custom_decorator(func):
def wrap(view, request):
if request.method.lower():
raise ValueError("Just for testing")
return func(view, request)
return wrap
class SomeAPIView(APIView):
@my_custom_decorator
def post(self, request):
return Response({"message": "Success"})
Note that the view function post(...)
is wrapped by the decorator @my_custom_decorator
.请注意,视图 function
post(...)
由装饰器@my_custom_decorator
包裹。 Noe, I want to write the test for this API and I tried like this不,我想为这个 API 写测试,我试过这样
from rest_framework.test import APITestCase
from django.urls import reverse
from unittest.mock import patch
class TestSomeAPIView(APITestCase):
@patch("sample.views.my_custom_decorator")
def test_decorator(self, mock_my_custom_decorator):
url = reverse("some-api-view")
response = self.client.post(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), {"message": "Success"})
This didn't mock the @my_custom_decorator
properly and thus gave me an exception.这没有正确模拟
@my_custom_decorator
,因此给了我一个例外。
Question : How can I mock the @my_custom_decorator
to retrieve a successful response?问题:如何模拟
@my_custom_decorator
以检索成功的响应?
This answer will work only if the test module gets initialized before the initialization of the view module.仅当测试模块在视图模块初始化之前初始化时,此答案才有效。 AFAIK, this kind of loading isn't configurable in Django.
AFAIK,这种加载在 Django 中是不可配置的。
This isn't a guaranteed solution but depending on your needs it may work to rewrite your decorator with a helper function that contains the logic that needs to be mocked.这不是一个有保证的解决方案,但根据您的需要,它可能会使用包含需要模拟的逻辑的帮助程序 function 重写您的装饰器。
For example:例如:
from rest_framework.views import APIView
from rest_framework.response import Response
def some_check_or_other_response(view, request):
if request.method.lower():
raise ValueError("Just for testing")
if some_other_condition:
return Response({})
def my_custom_decorator(func):
def wrap(view, request):
short_circuit_response = some_check_or_other_response(view, request)
if short_circuit_response:
return short_circuit_response
return func(view, request)
return wrap
class SomeAPIView(APIView):
@my_custom_decorator
def post(self, request):
return Response({"message": "Success"})
and then接着
class TestSomeAPIView(APITestCase):
@patch("sample.views.some_check_or_other_response")
def test_decorator(self, mock_some_check):
mock_some_check.return_value = ... # short-circuit with a return value
mock_some_check.side_effect = ValueError(...) # simulate an exception
... # etc
First, you will need to move my_custom_decorator
into another module, preferably within the same package as your views.py
.首先,您需要将
my_custom_decorator
移动到另一个模块中,最好在与您的views.py
相同的 package 中。
Then you need to:然后你需要:
Clear the module import cache for sample.decorators
, all modules within your app that import it, and your settings.ROOT_URLCONF
清除
sample.decorators
的模块导入缓存、应用程序中导入它的所有模块以及您的settings.ROOT_URLCONF
Clear the url cache that django uses internally清除django内部使用的url缓存
Monkey patch the decorator猴子修补装饰器
tests.py :测试.py :
import sys
from django.conf import settings
from django.urls import clear_url_caches
def clear_app_import_cache(app_name):
modules = [key for key in sys.modules if key.startswith(app_name)]
for module_name in modules:
del sys.modules[module_name]
try:
del sys.modules[settings.ROOT_URLCONF]
except KeyError:
pass
clear_url_caches()
class TestSomeAPIView(APITestCase):
@classmethod
def setUpClass(cls):
clear_app_import_cache('sample')
from sample import decorators
decorators.my_custom_decorator = lambda method: method
super().setUpClass()
@classmethod
def tearDownClass(cls):
# Make sure the monkey patch doesn't affect tests outside of this class.
# Might not be necessary
super().tearDownClass()
clear_app_import_cache('sample')
def test_decorator(self):
url = reverse("some-api-view")
response = self.client.post(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), {"message": "Success"})
First you need to move the decorator to a different module to get a change to mock it.首先,您需要将装饰器移动到不同的模块以进行更改以模拟它。
decorators.py装饰器.py
def my_custom_decorator(func):
def wrap(view, request):
if request.method.lower():
raise ValueError("Just for testing")
return func(view, request)
return wrap
views.py视图.py
from decorators import my_custom_decorator
class SomeAPIView(APIView):
@my_custom_decorator
def post(self, request):
return Response({"message": "Success"})
In your tests patch the decorator before it get applied, like this在您的测试中,在应用之前修补装饰器,就像这样
tests.py测试.py
from unittest.mock import patch
patch("decorators.my_custom_decorator", lambda x: x).start()
from rest_framework.test import APITestCase
from django.urls import reverse
class TestSomeAPIView(APITestCase):
def test_decorator(self):
url = reverse("some-api-view")
response = self.client.post(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), {"message": "Success"})
For simplicity, I think it is better to split the logic from the decorator to somewhere else.为简单起见,我认为最好将逻辑从装饰器拆分到其他地方。 So, I created a function named
_my_custom_decorator(...)
所以,我创建了一个名为
_my_custom_decorator(...)
的 function
def _my_custom_decorator(func, view, request): # do most of the decorator logic here!!! if request.method.lower(): raise ValueError("Just for testing") return func(view, request)
def my_custom_decorator(func):
def wrap(view, request):
return _my_custom_decorator(func, view, request) # calling the newly created function
return wrap
class SomeAPIView(APIView):
@my_custom_decorator # this decorator remain unchanged!!!
def post(self, request):
return Response({"message": "Success"})
and now, mock the _my_custom_decorator(...)
function in the tests,现在,在测试中模拟
_my_custom_decorator(...)
function,
def mock_my_custom_decorator(func, view, request): return func(view, request)
class TestSomeAPIView(APITestCase):
@patch("sample.views._my_custom_decorator", mock_my_custom_decorator)
def test_decorator(self):
url = reverse("some-api-view")
response = self.client.post(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), {"message": "Success"})
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.