Can't test password change in Django unittest

2019-07-26 19:51发布

问题:

I'm trying to write an integration test for a Django application to test if the password was changed from a rest API. However, after calling the password change API, testing for the new password doesn't work.

What I'm doing here is: I change the password to johnjohn through a rest call, then try to check if the password is actually changed

My unittest file

import json
import urllib
import urllib2

import requests
from django.contrib.auth.models import User

from django.test import TestCase
from adaptors.redis_class import DjangoRedis


class PasswordChange(TestCase):
    def setUp(self):
        User.objects.create_user(username='john', email='john@john.com', password='john')
        # class for redis access  
        r = DjangoRedis()
        r.set("email:john@john.com", '0' * 40, ex=3600)
        r.set('0' * 40, "email:john@john.com", ex=3600)
    def test_change_password(self):
        # Fake url I set through redis
        url = 'http://127.0.0.1:8000/reset/' + '0' * 40 + '/'
        r = requests.get(url)
        csrf_token = r.content.split('csrf_token')[1].split('"')[2]
        payload = {'csrfmiddlewaretoken': csrf_token, 'payload': json.dumps({'password': 'johnjohn'})}
        headers = {'Cookie': 'csrftoken=' + csrf_token}
        data = urllib.urlencode(payload)
        req = urllib2.Request(url, data, headers)
        response = urllib2.urlopen(req)
        json_res = json.loads(response.read())

        # This doesn't fail
        self.assertEqual(json_res['password_changed'], True)
        u = User.objects.get(username='john')

        # This fails
        self.assertEqual(u.check_password('johnjohn'),True)

My view

import hashlib
import random

from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404
from django.http import JsonResponse
from django.shortcuts import render
from django.views.generic import View

from adaptors.redis_class import DjangoRedis
from constants import PASSWORD_RESET
from tasks import send_email
from util.rest import get_payload
class ResetPage(View):
    def post(self, requests, reset_token):
        r = DjangoRedis()
        token = r.get(reset_token)
        if token is None:
            return JsonResponse({"token": False})
        payload = get_payload(requests.POST)
        try:
            email = token.split(':')[1].lower()
            u = User.objects.get(email=email)
            u.set_password(payload['password'])
            u.save()
            r.delete(reset_token)
            r.delete('email:' + email)
            return JsonResponse({'password_changed': True})
        except ValueError:
            return JsonResponse({"password_changed": False, 'error': "can't get your email from the database (E01)"})
        except ObjectDoesNotExist:
            return JsonResponse({"password_changed": False, 'error': "User doesn't exist (E02)"})

    def get(self, requests, reset_token):
        r = DjangoRedis()
        if not r.get(reset_token):
            raise Http404
        return render(requests, "login/reset.html", {'token': reset_token})

The unittest file is called rest_pass_login_tests.py in a unittests directory.

I run it by using the following command.

./manage.py test unittests/ --pattern="rest_pass_login_tests.py"

When I test normally through the browser, the password change works fine. However, when I try to check if the password was changed or not through a unittest, I get an invalid password.

What am I doing wrong?

回答1:

I was using dev server to run unittests. When I changed my password, I was actually posting to the dev server instead of the test one.

Here is my new unittest

import json

from django.contrib.auth.models import User
from django.test import Client
from django.test import TestCase

from adaptors.redis_class import DjangoRedis


class PasswordChange(TestCase):
    def setUp(self):
        User.objects.create_user(username='john', email='john@john.com', password='john')
        r = DjangoRedis()
        r.set("email:john@john.com", '0' * 40, ex=3600)
        r.set('0' * 40, "email:john@john.com", ex=3600)

    def test_change_password(self):
        c = Client()
        payload = {'payload': json.dumps({'password': 'johnjohn'})}

        url = '/reset/' + '0' * 40 + '/'
        res = c.post(url, payload)
        res = res.json()
        self.assertEqual(res['password_changed'], True)
        u = User.objects.get(username='john')
        self.assertEqual(u.check_password('johnjohn'), True)

If you're going to use selenium IDE, make sure you inherent from StaticLiveServerTestCase instead of unittest. Follow this example. Also, don't forget to change this variable self.base_url to self.live_server_url

This inside my setUp method

self.base_url = self.live_server_url

Thanks to fips, koterpillar for helping me on the IRC channel, and aks for commenting under me.



回答2:

You are not making an http post as your view expects.

Here is how to make a post using urllib2: Making a POST call instead of GET using urllib2

But, since you already use requests module why not simply do:

url = 'http://127.0.0.1:8000/reset/' + '0' * 40 + '/'
payload = {'csrfmiddlewaretoken': csrf_token, 'payload': json.dumps({'password': 'johnjohn'})}
headers = {'Cookie': 'csrftoken=' + csrf_token}
r = requests.post(url, data=payload, headers=headers)
json_res = json.loads(r.content)

And maybe try debugging/printing the value of json_res to check if there's anything else unexpected.

Also note, this is an integration test as you are testing through http, so your urls.py, views.py and even settings.py are being tested as part of it.