I have a django class based view that I'm decorating. Unfortunately that decorator makes outside calls to do status checks which is outside the scope of what the unit test should do so I want to override the decorator to do nothing during my unit tests. Here is my decorator:
decorators.py
def status_check(func):
@wraps(func)
def wrapped(request, *args, **kwargs):
uri = settings.SERVER_URI
status_code = None
bad_status = [404, 500]
try:
response = requests.head(uri)
except requests.ConnectionError as err:
LOGGER.error('Server is hosed! Oh Noes! Error: %s ' % (err))
raise Http404
except Exception as err:
LOGGER.error('Some crazy stuff is happening. Its Bad. '
'Error: %s' % (err))
raise Http404
status_code = response.status_code
if not status_code or status_code in bad_status:
LOGGER.error('Awww Snap! Server is not happy: %s' % (status_code))
raise Http404
return func(request, *args, **kwargs)
return wrapped
views.py
class HandleFoo(DetailView):
@method_decorator(status_check)
def post(self, request):
# do stuff here
tests.py
class RunTest(TestCase):
def test_post(self):
post_data = json.dumps({'stuff': 'vodka', 'things': 'tonic'})
resp = self.client.post(self.foo_uri,
post_data,
content_type='application/json',
)
self.assertEqual(resp.status_code, 200)
So is there a way for me to either override the decorator or can I bypass it altogether? I'm rather stumped on this.
EDIT Tried mocking out the request using the following from krak3n:
@patch('app.views.method_decorator.status_check', lambda func: func)
@patch('app.views.status_check', lambda func: func)
@patch('app.decorators.status_check', lambda func: func)
@patch('app.views.HandleFoo.post', lambda func: func)
The last method gets me the closest thus far, but it ends up throwing a stacktrace:
Traceback (most recent call last):
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/mock.py", line 1201, in patched
return func(*args, **keywargs)
File "/Users/squiddly/projects/tests/app/tests.py", line 165, in test_post
content_type='application/json',
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/test/client.py", line 463, in post
response = super(Client, self).post(path, data=data, content_type=content_type, **extra)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/test/client.py", line 297, in post
return self.request(**r)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/test/client.py", line 406, in request
response = self.handler(environ)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/test/client.py", line 111, in __call__
response = self.get_response(request)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/core/handlers/base.py", line 178, in get_response
response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/core/handlers/base.py", line 224, in handle_uncaught_exception
return callback(request, **param_dict)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/utils/decorators.py", line 91, in _wrapped_view
response = view_func(request, *args, **kwargs)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/views/defaults.py", line 41, in server_error
return http.HttpResponseServerError(template.render(Context({})))
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/base.py", line 140, in render
return self._render(context)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/test/utils.py", line 65, in instrumented_test_render
return self.nodelist.render(context)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/base.py", line 830, in render
bit = self.render_node(node, context)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/debug.py", line 74, in render_node
return node.render(context)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/loader_tags.py", line 124, in render
return compiled_parent._render(context)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/test/utils.py", line 65, in instrumented_test_render
return self.nodelist.render(context)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/base.py", line 830, in render
bit = self.render_node(node, context)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/debug.py", line 74, in render_node
return node.render(context)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/loader_tags.py", line 156, in render
return self.render_template(self.template, context)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/loader_tags.py", line 138, in render_template
output = template.render(context)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/base.py", line 140, in render
return self._render(context)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/test/utils.py", line 65, in instrumented_test_render
return self.nodelist.render(context)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/base.py", line 830, in render
bit = self.render_node(node, context)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/debug.py", line 74, in render_node
return node.render(context)
File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/base.py", line 1185, in render
_dict = func(*resolved_args, **resolved_kwargs)
File "/Users/squiddly/projects/tests/app/templatetags/app_extras.py", line 40, in get_data
if request.session.has_key('start_time'):
AttributeError: 'str' object has no attribute 'session'
I think your gonna have to get into the dark underworld of Mock, but once you get your head around it (if you haven't already) the dark underworld turns into a bright blue heavenly sky of mockiness.
You could use the patch module of Mock to to patch this decorator so your views using it can become more testable: http://www.voidspace.org.uk/python/mock/patch.html. Personally I have not tried mocking a decorator before but it should work...
@patch('python.path.to.decorator', new_callable=PropertyMock)
def my_test(self, decorator_mock):
# your test code
Give that a whirl.
You can read about the patch module in Mock here: http://www.voidspace.org.uk/python/mock/patch.html
new_callable=PropertyMock is probably not the right thing to do for patching a decorator.
Perhaps try:
@patch('python.path.to.decorator', lambda: func: func)
def my_test(self):
# your test code
This should in theory patch the decorator so it just returns the function back rather than does all the stuff you have in wrapped.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With