Django的self.client.login(...)在单元测试中无法正常工作

103

我已经用两种方式为我的单元测试创建了用户:

1)创建一个名为“auth.user”的fixture,大致如下:

    { 
        "pk": 1, 
        "model": "auth.user", 
        "fields": { 
            "username": "homer", 
            "is_active": 1, 
            "password": 
"sha1$72cd3$4935449e2cd7efb8b3723fb9958fe3bb100a30f2", 
            ... 
        } 
    }

我省略了看似不重要的部分。

2)在setUp函数中使用'create_user'(尽管我更愿意将所有内容都保留在我的固件类中):

def setUp(self): 
       User.objects.create_user('homer', 'ho...@simpson.net', 'simpson') 
请注意两种情况下密码均为simpson。
我已经多次验证了这些信息已经正确地加载到测试数据库中。我可以使用User.objects.get获取用户对象。我可以使用'check_password'验证密码是否正确。该用户是活跃的。
然而,无论如何,self.client.login(username ='homer',password ='simpson')都会失败。我对此感到困惑。我认为我已经阅读了与此相关的每个互联网讨论。有人能帮忙吗?
我的单元测试中的登录代码如下:
    login = self.client.login(username='homer', password='simpson') 
    self.assertTrue(login) 

谢谢。


1
你收到了什么错误信息? - zs2020
测试用例在“self.assertTrue(login)”这一行失败了;login()函数返回False。 - thebossman
1
我基本上复制并粘贴了您的第二个变体,它在Django 1.3上运行。您能否发布包括导入在内的整个代码? - Liorsion
这个问题已经被埋在我不再访问的代码库中。如果我遇到这个问题,我一定会跟进,但是记录一下,它是在早期版本的Django中出现的,我想是1.0.2。 - thebossman
8个回答

173

无法运行的代码:

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

user = User.objects.create(username='testuser', password='12345')

c = Client()
logged_in = c.login(username='testuser', password='12345')

为什么它不起作用?

在上面的代码片段中,当创建“用户”时,实际密码哈希设置为“12345”。当客户端调用“登录”方法时,“password”参数的值“12345”通过哈希函数传递,导致类似于...的结果。
hash('12345') = 'adkfh5lkad438....'

这时将与数据库中存储的哈希值进行比较,如果客户端被拒绝访问,因为'adkfh5lkad438....' != '12345'

解决方案

正确的做法是调用set_password函数,该函数通过哈希函数传递给定字符串并将结果存储在User.password中。

此外,在调用set_password之后,我们必须将更新的User对象保存到数据库中:

user = User.objects.create(username='testuser')
user.set_password('12345')
user.save()

c = Client()
logged_in = c.login(username='testuser', password='12345')

或者,使用更专业的API:

# this encodes the password and calls save()
user = User.objects.create_user(username='testuser', password='12345')

c = Client()
logged_in = c.login(username='testuser', password='12345')

如果您想要一个超级用户,请使用create_superuser

61
您可以使用User.objects.create_superuser()User.objects.create_user(),它们会执行与此set_password()调用完全相同的操作。 - vdboor
当您登录一个人时,怎么样? - rocky raccoon
补充@vdboor的评论:请注意,自Django 1.4以来,User.objects.get_or_create()也执行所需的set_password()调用。 - furins
1
@vdboor 我同意应该使用 User.objects.create_user() - Tiago Martins Peres

76

更简单的方法是使用Django 1.9中新增的force_login

force_login(user, backend=None)
例如:
class LoginView(TestCase):
    def setUp(self):
        self.client.force_login(User.objects.get_or_create(username='testuser')[0])

1
由于某些原因,被接受的答案对我不起作用,但这个答案却可以(而且更简洁)。 - Romain Reboulleau
1
由于某些原因,force_login 对我没有起作用(页面仍然返回 302 重定向)。唉,算了。 - dfrankow

6

2
即使在中间件类中包含 django.contrib.sessions.middleware.SessionMiddleware,您仍无法通过 django.test.Client 登录。我花了一周时间才找到这个答案。 - pupil

5
您能够检查如下内容吗,
from django.test import TransactionTestCase, Client

class UserHistoryTest(TransactionTestCase):
    self.user = User.objects.create(username='admin', password='pass@123', email='admin@admin.com')
    self.client = Client() # May be you have missed this line

    def test_history(self):
        self.client.login(username=self.user.username, password='pass@123')
        # get_history function having login_required decorator
        response = self.client.post(reverse('get_history'), {'user_id': self.user.id})
        self.assertEqual(response.status_code, 200)

这个测试用例对我来说是有效的。


1
如果您正在使用 rest_framework,请确保启用了基于会话的身份验证。这就是我的问题所在。
打开您的 settings.py 文件并检查 REST_FRAMEWORK -> DEFAULT_AUTHENTICATION_CLASSES 是否包括 SessionAuthentication
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.TokenAuthentication",
        "rest_framework.authentication.SessionAuthentication",
    ],
    ...
}

看起来登录方法使用了纯Django基于session的方法,因此如果你仅使用rest_framework的token auth,那么将会失败。


0
from django.test import TestCase
from django.contrib.auth.models import User
from django.test import Client
class MyProfile(TestCase):
    @classmethod
    def setUpClass(self):
        self.username = 'dummy' + data + '@gmail.com'
        self.password = 'Dummy@123'
        user = User.objects.create(username=self.username)
        user.set_password(self.password)
        user.save()
        c = Client()
        self.client_object = c.login(username=self.username, password=self.password)
        self.content_type = "application/json"
        response = self.client_object.post('/api/my-profile/', content_type=self.content_type)

0

如果您只需要在测试用例期间拥有经过身份验证的用户,您可以使用force_login,它不需要任何身份验证属性,只需传递用户对象即可。

    def test_something_view(self):
        client = Client()
        client.force_login(self.user)
        response = client.post(reverse('your custom url'), follow=True)
        self.assertEqual(response.status_code, 200)


-4

如果还有人在关注这个问题,我认为属性'is_staff'和'is_active'应该保持为True,以便成功登录......

self.user = User.objects.create(username='testuser',password='pwd',is_active=1,is_staff=1)


4
这很顺利...self.user = User.objects.create(username='testuser', password='12345', is_active=True, is_staff=True, is_superuser=True) self.user.set_password('hello') self.user.save() user = authenticate(username='testuser', password='hello') login = self.c.login(username='testuser', password='hello') self.assertTrue(login) - Arindam Roychowdhury
这与 is_staff 没有任何关系,它定义了是否授予对管理员面板的访问权限。正如您在最初的帖子中所看到的数据已经具有 is_active = 1,这也是 User.objects.create() 的默认值。 - jnns
1
@arindamroychowdhury - 哇,那真的起作用了...(指你的评论) - Richard de Wit
1
可能是版本问题,但我不得不注释掉你的身份验证调用。除此之外,你的评论运行得很好!然而,在进一步的测试中,我发现is_active、is_superuser和is_staff都是不必要的。真正起作用的是set_password的调用。 - dolphus333

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接