Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django - Testing - Problems with @login_required decorator

Problem

UPDATE: This issue, it turns out, has little to do with the @login_required decorator!

I am getting finicky behavior when I try to test views that are decorated with @login_required.

I have one test that is actually able to go to a view decorated with @login_required (a password change view). A different test, however, always gets redirected to login. No matter which way I've tried to re-write it, it won't let my test user through, even though I am logging the user in and asserting that user.is_authenticated() beforehand.

Here's the relevant snippet of the test that's having issues:

# Log user in
self.client.login(username=user.username, password=user.password)
self.assertTrue(user.is_authenticated())

# Go to account_edit view
url = reverse('account_edit')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'users/account_edit_form.html')

This inevitably redirects to the login view, as if the user were not logged in.

I can confirm that this behavior is not reflected in the "normal" functioning of the app; the decorator works exactly as expected when I actually run the server and then navigate to this view (i.e. it allows access when logged in, and redirects to login view).

Using Django 1.3.1. I am using the testrunner from django-nose, but I can confirm that this issue happens regardless of which testrunner I use.

Also, I found several previous questions, but the solutions suggested were either particular to older versions of Django, or not helpful in this case (see here for example).

Solution (combining two good answers)

I received two very good answers to this question, both of which highlighted important oversights in the snippet I posted. The issue had nothing to do with @login_required's behavior, and everything to do with (a) signing the user in wrong and (b) checking the user authentication wrong.

I was having a hard time picking which answer to accept, but after a little thought, I've decided to accept Konrad Hałas's answer, since it pinpoints the crucial oversight on my part, that was the root of unexpected behavior.

Nonetheless, I would have figured this out much sooner if I had not been using the faulty test line self.assertTrue(user.is_authenticated()). So to emphasize that the solution was actually two parts, here are the two steps to fixing the problematic code:

# Log user in *NOTE: Password needs to be raw (from Konrad's answer)
self.client.login(username=user.username, password="pass")
self.assertTrue(user.is_authenticated()) # <-- still not correct

The assertion line is still faulty because a valid user always satisfies user.is_authenticated(). See Alasdair's info for an explanation of this gotcha. So step two of fixing this code is:

# Log user in *NOTE: Password needs to be raw (from Konrad's answer)
login_successful = self.client.login(username=user.username, password="pass")
self.assertTrue(login_successful) # Much better! (see Alasdair's answer)

Finally, should you need to test that your user was logged in without using client.login (i.e. testing a login form), this should work:

self.assertTrue(response.context['user'].is_authenticated())
like image 891
m_floer Avatar asked Aug 28 '12 19:08

m_floer


2 Answers

user.password is password hash. You can't log in with it. You need to use original password:

self.client.login(username=user.username, password='<user password>')
like image 115
Konrad Hałas Avatar answered Nov 10 '22 16:11

Konrad Hałas


The gotcha here is that the is_authenticated method always returns True for a User instance.

In the template or view it can be useful - if request.user.is_authenticated() is True, then you have a real user, not an AnonymousUser, and can proceed accordingly. However, if you start off with a User object, as you have done, then is_authenticated() can be confusing!

You can check the return value of the test client's login() method to test whether it was successful.

login_successful = c.login(username='fred', password='secret')
self.assertTrue(login_successful)
like image 34
Alasdair Avatar answered Nov 10 '22 17:11

Alasdair