38

I have started using Django's testing framework, and everything was working fine until I started testing authenticated pages.

For the sake of simplicity, let's say that this is a test:

class SimpleTest(TestCase):
    def setUp(self):
        user = User.objects.create_user('temporary', 'temporary@gmail.com', 'temporary')

    def test_secure_page(self):
        c = Client()
        print c.login(username='temporary', password='temporary')
        response = c.get('/users/secure/', follow=True)
        user = User.objects.get(username='temporary')
        self.assertEqual(response.context['email'], 'temporary@gmail.com')

After I run this test, it fails, and I see that printing return value of login() returns True, but response.content gets redirected to login page (if login fails authentication decorator redirects to login page). I have put a break point in decorator that does authentication:

def authenticate(user):
    if user.is_authenticated():
        return True
    return False

and it really returns False. Line 4 in test_secure_page() properly retrieves user.

This is the view function:

@user_passes_test(authenticate, login_url='/users/login')
def secure(request):
    user = request.user
    return render_to_response('secure.html', {'email': user.email})

Of course, if I try to login through application (outside of test), everything works fine.

kevin
  • 2,172
  • 3
  • 18
  • 19
  • 1
    And please post the code of the view you're testing. – Filip Jukić Sep 09 '11 at 21:26
  • @S.Lott I was not testing my login page (although I tried that as well, but that's not working neither), but rest of the "secure" part of the system. For that reason, I tried using login(). – kevin Sep 09 '11 at 21:49
  • 1
    @kevin. The issue is that the post to login creates a cookie that is then used by the client. No cookie, no secure access. AFAIK, the `login()` function doesn't create the cookie and return it to the client. Can you try to reproduce the examples in the Django documentation and see if they work for you? – S.Lott Sep 12 '11 at 13:49

3 Answers3

35

The problem is that you're not passing RequestContext to your template.

Also, you probably should use the login_required decorator and the client built in the TestCase class.

I'd rewrite it like this:

#views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django.contrib.auth import get_user_model

@login_required(login_url='/users/login')
def secure(request):
    user = request.user
    return render(request, 'secure.html', {'email': user.email})



#tests.py
class SimpleTest(TestCase):
    def setUp(self):
        User = get_user_model()
        user = User.objects.create_user('temporary', 'temporary@gmail.com', 'temporary')

    def test_secure_page(self):
        User = get_user_model()
        self.client.login(username='temporary', password='temporary')
        response = self.client.get('/manufacturers/', follow=True)
        user = User.objects.get(username='temporary')
        self.assertEqual(response.context['email'], 'temporary@gmail.com')
Flimm
  • 136,138
  • 45
  • 251
  • 267
Filip Jukić
  • 1,174
  • 7
  • 11
  • 1
    Why do you think that not passing RequestContext should be a problem (context is given to render_to_response())? It works outside of testing framework when this is invoked from browser (btw, I tried passing RequestContext, but the same thing happens). Also, authentication happens before view is entered. I tried @login_required and client from TestCase class as well, but again same thing happens. – kevin Sep 09 '11 at 22:22
  • I made it work by changing: self.assertEqual(response.context['user'].email, 'temporary@gmail.com') – cor Jun 04 '14 at 11:26
13

It can often be useful to use a custom auth backend that bypassess any sort of authentication during testing:

from django.contrib.auth import get_user_model

class TestcaseUserBackend(object):
    def authenticate(self, testcase_user=None):
        return testcase_user

    def get_user(self, user_id):
        User = get_user_model()
        return User.objects.get(pk=user_id)

Then, during tests, add yourapp.auth_backends.TestcaseUserBackend to your AUTHENTICATION_BACKENDS:

AUTHENTICATION_BACKENDS = [
    "akindi.testing.auth_backends.TestcaseUserBackend",
]

Then, during tests, you can simply call:

from django.contrib.auth import login
user = User.objects.get(…)
login(testcase_user=user)
Flimm
  • 136,138
  • 45
  • 251
  • 267
David Wolever
  • 148,955
  • 89
  • 346
  • 502
1

Token based authentication: I was in same situation. I found solution in which actually I did generate a user for login purpose in setUp method. Then later in the test methods, I tried to get the token and passed it along with request data.

setUp:

  1. create a user
self.pravesh = User.objects.create(
     email='psj.aaabbb@gmail.com',
     first_name='Pravesh',
     last_name='aaabbb',
     phone='5456165156',
     phonecountrycode='91'
)
  1. set password for the user
self.password = 'example password'
self.pravesh.set_password(self.password)

test_method:

  1. create client
client.login(email=self.pravesh.email, password=self.password)
  1. get token (in case of token auth)
token = Token.objects.create(user=self.pravesh)
  1. pass login information
response = client.post(
    reverse('account:post-data'),
    data = json.dumps(self.data),
    HTTP_AUTHORIZATION='Token {}'.format(token),
    content_type = 'application/json'
)
GabLeRoux
  • 16,715
  • 16
  • 63
  • 81
OpenHub
  • 11
  • 1
  • I tries your solution but I still get error 401, will you plase check my question and help me?https://stackoverflow.com/questions/56311866/djnago-unittest-clien-get-error-401-even-if-i-send-token – user907988 May 26 '19 at 08:40