7

I'm doing some (isolated) unit test for a view which is decorated with "login_required". Example:

@login_required
def my_view(request):
    return HttpResponse('test')

Is it possible to test that the "my_view" function is decorated with "login_required"?

I know I can test the behaviour (anonymous user is redirected to login page) with an integration test (using the test client) but I'd like to do it with an isolated test.

Any idea?

Thanks!

goathi
  • 353
  • 1
  • 4
  • 9
  • In your test make a `request` to the url asociated to `myview` with a `AnonymousUser`, it should return a `403 Forbidden Status` – Gocht Jun 19 '15 at 23:16
  • Try [this blog post](http://schinckel.net/2012/01/20/get-decorators-wrapping-a-function/) – Alasdair Jun 19 '15 at 23:18
  • @Gocht, thanks but as I said above I know I can test the behaviour doing an integration test with the Django Test Client but in this case I want to do it with an isolated pure unit test. – goathi Jun 20 '15 at 13:39
  • @Alasdair, interesting but I couldn't find a solution yet. I see that when the view is not decorated the function has no "func_closure" and when it does func_closure returns a tuple with 4 items. The last item is actually the view function "my_view", so I'm not sure if I should consider this as "the view is decorated by the login_required function" because I can't find any reference to "login_required" using func_closure and cell_contents – goathi Jun 20 '15 at 13:40

3 Answers3

5

Sure, it must be possible to test it in some way. It's definitely not worth it, though. Writing a fully isolated unit test to check that the decorator is applied will only result in a very complicated test. There is a way higher chance that the test will be wrong than that the tested behaviour is wrong. I would strongly discourage it.

The easiest way to test it is to use Django's Client to fake a request to the associated url, and check for a redirect. If you're using any of Django's testcases as your base class:

class MyTestCase(django.test.TestCase):
    def test_login_required(self):
        response = self.client.get(reverse(my_view))
        self.assertRedirects(response, reverse('login'))

A slightly more complicated, but a bit more isolated test would be to call the view directly using the RequestFactory to create a request object. assertRedirects() won't work in this case, since it depends on attributes set by the Client:

from django.test.client import RequestFactory

class MyTestCase(django.test.TestCase):
    @classmethod
    def setUpClass(cls):
        super(MyTestCase, cls).setUpClass()
        self.rf = RequestFactory()

    def test_login_required(self):
        request = self.rf.get('/path/to/view')
        response = my_view(request, *args, **kwargs)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response['Location'], login_url)
        ...
knbk
  • 52,111
  • 9
  • 124
  • 122
  • Thanks but as I said above I know I can test the behaviour doing an integration test with the Django Test Client but in this case I want to do it with an isolated pure unit test. – goathi Jun 20 '15 at 13:38
  • @goathi Unit testing if the decorator is applied is complicated and not worth it IMO. I've updated my answer with a functional test. I wouldn't use more granularity than this to test for `login_required`. – knbk Jun 20 '15 at 14:04
  • Well, a good reason for checking if the decorator is defined is that I don't have to test the behaviour (redirecting user to login page if he's not authenticated) which is already tested by Django. I agree with your point about it not being worth though. Or maybe I should not use decorators at all? – goathi Jun 20 '15 at 14:40
  • I would use the decorator, it's easy to use and tested by Django. You only need to test that the decorator is applied to your view, which is implied by the correct behaviour if an unauthenticated request comes in. That's really all there is to test. – knbk Jun 20 '15 at 14:46
3

Use Django's Test Client to check for proper redirects in case a user is loggedin and when not loggedin.

from django.test import TestCase
from django.core.urlresolvers import reverse

class TestLoginRequired(django.test.TestCase):

    def test_redirects_to_login_page_on_not_loggedin(self):
        response = self.client.get(reverse(my_view))
        self.assertRedirects(response, reverse('login_page'))

    def test_redirects_to_test_page_on_loggedin(self):
        self.client.login(username='my_username', password='my_password')
        response = self.client.get(reverse(my_view))
        self.assertRedirects(response, reverse('test'))

MOCK Library:

For an isolated test or 'pure' unit testing, you can use the mock module.

Mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects and make assertions about how they have been used.
Mock is based on the ‘action -> assertion’ pattern instead of ‘record -> replay’ used by many mocking frameworks.

You will have to create a mock object. Mock objects create all attributes and methods as you access them and store details of how they have been used. You can configure them, to specify return values or limit what attributes are available, and then make assertions about how they have been used:

The tests with mock objects will test only whether my_view function is decorated with login_required. You don't need to setup other attributes.

Check out the docs on how to write tests using mock objects using this link.

Also, following SO links might help on how to monkey-patch a decorator.

Community
  • 1
  • 1
Rahul Gupta
  • 46,769
  • 10
  • 112
  • 126
  • Thanks but as I said above I know I can test the behaviour doing an integration test with the Django Test Client but in this case I want to do it with an isolated pure unit test. – goathi Jun 20 '15 at 13:38
  • Check out the `mock` library for writing isolated pure unit tests. I think that might help. – Rahul Gupta Jun 20 '15 at 14:33
  • Thanks @Rahul , I know the Mock library and I use it a lot but I couldn't use it to test that the decorator is defined. – goathi Jun 20 '15 at 14:42
  • You need to monkey-patch the decorator. I haven't had much experience on that. Try out these links: http://stackoverflow.com/questions/14023763/how-can-i-monkey-patch-a-decorator-in-djangos-models-while-testing http://stackoverflow.com/questions/7667567/can-i-patch-a-python-decorator-before-it-wraps-a-function – Rahul Gupta Jun 20 '15 at 14:59
  • I think you can test whether `my_view` is called as an arg in the `login_required` decorator wrapper. – Dzhuang Dec 21 '17 at 18:09
-2

Using requests library, if you don't pass it a auth cookie you can test that login is required based on whether it returns 401/403/200/whatever

import requests
req = requests.get("http://yoururl.com")
if req.status_code ==200:
    print "login not needed apparently"
else:
    print "check for other status codes or redirect"
John
  • 2,410
  • 1
  • 19
  • 33
  • Thanks but as I said above I know I can test the behaviour doing an integration test with the Django Test Client but in this case I want to do it with an isolated pure unit test. – goathi Jun 20 '15 at 13:38