Skip to content

Instantly share code, notes, and snippets.

@benoitbryon
Last active September 16, 2020 14:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save benoitbryon/5156512 to your computer and use it in GitHub Desktop.
Save benoitbryon/5156512 to your computer and use it in GitHub Desktop.
Sample code to illustrate a blog post about testing view decorators in Django apps.
"""Testing Django view decorators.
This code was originally published as part of an article at
http://tech.novapost.fr/django-testing-view-decorators-en.html
To run this file:
.. code-block:: sh
virtualenv --distribute --no-site-packages testing
cd testing/
bin/pip install --index-url=https://simple.crate.io django mock
wget -O sample.py https://gist.github.com/benoitbryon/5156512/raw/
bin/python -m unittest sample
"""
from functools import wraps
import unittest
try:
from unittest import mock
except ImportError:
import mock
from django.conf import settings
# Django needs some early configuration.
settings.configure(
DATABASES={}, # Required to load ``django.views.generic``.
DEFAULT_CHARSET='utf-8', # Required to instanciate an HttpResponse.
)
from django.http import HttpResponse, HttpResponseForbidden
from django.utils.decorators import available_attrs
from django.views.generic.base import TemplateView
class MockViewTestCase(unittest.TestCase):
def test_stub(self):
# Setup.
request = 'fake request'
view = mock.MagicMock(return_value='fake response')
# Run.
response = view(request)
# Check.
view.assert_called_once_with(request)
self.assertEqual(response, view.return_value)
def hello_world(view_func):
"""Run the decorated view, but return "Hello world!"."""
def decorated_view(request, *args, **kwargs):
view_func(request, *args, **kwargs)
return HttpResponse(u'Hello world!')
return decorated_view
class HelloWorldTestCase(unittest.TestCase):
def test_hello_decorator(self):
"""hello_world decorator runs view and returns greetings."""
# Setup.
request = 'fake request'
request_args = ('foo', )
request_kwargs = {'bar': 'baz'}
view = mock.MagicMock(return_value='fake response')
# Run.
decorated = hello_world(view)
response = decorated(request, *request_args, **request_kwargs)
# Check.
# View was called.
view.assert_called_once_with(request, *request_args, **request_kwargs)
# But response is "Hello world!".
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, u"Hello world!")
class HttpResponseUnauthorized(HttpResponse):
status_code = 401
class UnauthorizedView(TemplateView):
response_class = HttpResponseUnauthorized
template_name = '401.html'
class ForbiddenView(TemplateView):
response_class = HttpResponseForbidden
template_name = '403.html'
def authenticated_user_passes_test(test_func,
unauthorized=UnauthorizedView.as_view(),
forbidden=ForbiddenView.as_view()):
"""Make sure user is authenticated and passes test.
This is an adaptation of
``django.contrib.auth.decorators.user_passes_test`` where:
* if user is anonymous, the request is routed to ``unauthorized`` view.
No additional tests are performed in that case.
* if user is authenticated and doesn't pass ``test_func ``test, the
request is routed to ``forbidden`` view.
* else, request and arguments are passed to decorated view.
Typical ``unauthorized`` view returns HTTP 401 status code and gives the
user an opportunity to log in: access may be granted after
authentication.
Typical ``forbidden`` view returns HTTP 403 status code: with active user
account, access is refused. As explained in rfc2616, 401 and 403 status
codes could be suitable.
.. seealso::
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4
* https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
"""
def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view(request, *args, **kwargs):
if not request.user.is_authenticated():
return unauthorized(request)
if not test_func(request.user):
return forbidden(request)
return view_func(request, *args, **kwargs)
return _wrapped_view
return decorator
class AuthenticatedUserPassesTestTestCase(unittest.TestCase):
def setUp(self):
"""Common setup: fake request, stub views, stub user test function."""
super(AuthenticatedUserPassesTestTestCase, self).setUp()
# Fake request and its positional and keywords arguments.
self.request = mock.MagicMock()
self.request.user.is_authenticated = mock.MagicMock()
self.request_args = ['fake_arg']
self.request_kwargs = {'fake': 'kwarg'}
# Mock user test function.
self.test_func = mock.MagicMock()
# Mock unauthorized and forbidden views.
self.unauthorized_view = mock.MagicMock(
return_value=u"401 - You may log in.")
self.forbidden_view = mock.MagicMock(
return_value=u"403 - Insufficient privileges.")
# Mock the view to decorate.
self.authorized_view = mock.MagicMock(
return_value=u"200 - Greetings, Professor Falken.")
def run_decorated_view(self, is_authenticated=True, user_passes_test=True):
"""Setup, decorate and call view, then return response."""
# Custom setup.
self.request.user.is_authenticated.return_value = is_authenticated
self.test_func.return_value = user_passes_test
# Get decorator.
decorator = authenticated_user_passes_test(
self.test_func,
unauthorized=self.unauthorized_view,
forbidden=self.forbidden_view)
# Decorate view.
decorated_view = decorator(self.authorized_view)
# Return response.
return decorated_view(self.request,
*self.request_args,
**self.request_kwargs)
def test_unauthorized(self):
"""authenticated_user_passes_test first tests user authentication."""
response = self.run_decorated_view(is_authenticated=False)
# Check: unauthorized view was called with request as unique positional
# argument.
self.unauthorized_view.assert_called_once_with(self.request)
self.assertEqual(response, self.unauthorized_view.return_value)
# Test func was not called.
self.assertFalse(self.test_func.called)
# Of course, authorized and forbidden views were not called.
self.assertFalse(self.authorized_view.called)
self.assertFalse(self.forbidden_view.called)
def test_test_func_args(self):
"""authenticated_user_passes_test passes user instance to test func."""
self.run_decorated_view(is_authenticated=True)
# Check: test_func was called with one argument: user instance.
self.test_func.assert_called_once_with(self.request.user)
def test_forbidden(self):
"""authenticated_user_passes_test runs forbidden view if user fails."""
response = self.run_decorated_view(is_authenticated=True,
user_passes_test=False)
# Check: forbidden view was called with request as unique positional
# argument.
self.forbidden_view.assert_called_once_with(self.request)
self.assertEqual(response, self.forbidden_view.return_value)
# Of course, authorized and unauthorized views were not triggered.
self.assertFalse(self.authorized_view.called)
self.assertFalse(self.unauthorized_view.called)
def test_authorized(self):
"""authenticated_user_passes_test runs view if user passes test."""
response = self.run_decorated_view(is_authenticated=True,
user_passes_test=True)
# Check: decorated view has been called, request and other arguments
# were proxied as is, response was not altered.
self.authorized_view.assert_called_once_with(self.request,
*self.request_args,
**self.request_kwargs)
self.assertEqual(response, self.authorized_view.return_value)
# Of course, forbidden and unauthorized views were not triggered.
self.assertFalse(self.forbidden_view.called)
self.assertFalse(self.unauthorized_view.called)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment