I am testing my authentication with the django.test.Client and two tests cases fail because once I test my test_login_success test case, the other tests fail because the user remains authenticated, even when I am instantiating a new client in the class setUp and even deleting the user in the tearDown.
My code:
from django.test import Client, TestCase
from app.users.models import User
class TestLogin(TestCase):
def setUp(self):
super().setUp()
self.email = 'test#test.com'
self.password = 'SomeRandomPass96'
User.objects.create_user(email=self.email, password=self.password)
self.client = Client()
def tearDown(self):
User.objects.filter(email=self.email).delete()
super().tearDown()
def test_not_authenticated(self): # success the first time, fails after test_login_success is executed for the first time.
user = User.objects.get(email=self.email)
assert not user.is_authenticated
def test_login_success(self): # always success
self.client.post(
'/users/login/',
{'email': self.email, 'password': self.password}
)
user = User.objects.get(email=self.email)
assert user.is_authenticated
def test_login_wrong_credentials(self): # success the first time, fails after test_login_success is executed for the first time.
self.client.post(
'/users/login/',
{'email': self.email, 'password': 'wrongPassword123'}
)
user = User.objects.get(email=self.email)
assert not user.is_authenticated
Maybe you can use the method logout from Client class.
this method "Log out the user by removing the cookies and session object."
Tell me if that works.
The error: user.is_authenticated is not doing what is expected in this code. Since the user in request object in the view will be an AnonymousUser or a User instance depending on the case if it's authenticated and I was accessing directly to a User instance from the database.
The proper way is to access to the user from the request object, and since we don't have the view context in the test case, Django provides it returned in the response like so:
def test_not_authenticated(self):
response = self.client.get('')
user = response.wsgi_request.user
assert not user.is_authenticated
Related
The reason I am asking is I have the fields username,password and otp_token. The otp_token is challenging to create, therefore I was wondering if there is a way create an authenticated user at the beginning of the test file to carry out the rest of the django tests as an authenticated user?
For example, how to pass a logged in user to the following
def some_test(self):
login = self.client.login(username='testUser', password='testPassword')
response = self.client.get(reverse('page1:conent1'))
self.assertEqual(response.status_code, 200)
related question
Not sure how are you generating that token, but I think you can use some dummy data in test
from django.test import TestCase, Client
def setUp(self):
self.user = User.objects.create(username='<USERNAME>',
email='<EMAIL>', otp_token='<YOUR_VALUE>')
self.user.set_password(<PASSWORD>)
self.user.save()
self.client = Client()
def some_test(self):
login = self.client.login(username='<USERNAME>', password='<PASSWORD>')
...
I have created users for my unit tests in two ways:
1) Create a fixture for "auth.user" that looks roughly like this:
{
"pk": 1,
"model": "auth.user",
"fields": {
"username": "homer",
"is_active": 1,
"password":
"sha1$72cd3$4935449e2cd7efb8b3723fb9958fe3bb100a30f2",
...
}
}
I've left out the seemingly unimportant parts.
2) Use 'create_user' in the setUp function (although I'd rather keep
everything in my fixtures class):
def setUp(self):
User.objects.create_user('homer', 'ho...#simpson.net', 'simpson')
Note that the password is simpson in both cases.
I've verified that this info is correctly being loaded into the test database time and time again. I can grab the User object using User.objects.get. I can verify the password is correct using 'check_password.' The user is active.
Yet, invariably, self.client.login(username='homer', password='simpson') FAILS. I'm baffled as to why. I think I've read every single Internet discussion pertaining to this. Can anybody help?
The login code in my unit test looks like this:
login = self.client.login(username='homer', password='simpson')
self.assertTrue(login)
Thanks.
The code that doesn't work:
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')
Why doesn't it work?
In the snippet above, when the User is created the actual password hash is set to be 12345. When the client calls the login method, the value of the password argument, 12345, is passed through the hash function, resulting in something like
hash('12345') = 'adkfh5lkad438....'
This is then compared to the hash stored in the database, and the client is denied access because 'adkfh5lkad438....' != '12345'
The Solution
The proper thing to do is call the set_password function, which passes the given string through the hash function and stores the result in User.password.
In addition, after calling set_password we must save the updated User object to the database:
user = User.objects.create(username='testuser')
user.set_password('12345')
user.save()
c = Client()
logged_in = c.login(username='testuser', password='12345')
An easier way is to use force_login, new in Django 1.9.
force_login(user, backend=None)
For example:
class LoginView(TestCase):
def setUp(self):
self.client.force_login(User.objects.get_or_create(username='testuser')[0])
Check that django.contrib.sessions is added to INSTALLED_APPS because client.login() checks that it is and will always return false if it is not:
https://docs.djangoproject.com/es/1.9/topics/http/sessions/#enabling-sessions
Can you check like below,
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)
This test case worked for me.
If you are using rest_framework, make sure session-based authentication is enabled. That was my issue.
Go to your settings.py file and check that REST_FRAMEWORK -> DEFAULT_AUTHENTICATION_CLASSES includes SessionAuthentication:
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.TokenAuthentication",
"rest_framework.authentication.SessionAuthentication",
],
...
}
It looks like the login method uses the vanilla Django session-based approach, so if you were only using rest_framework's token auth that's going to fail.
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)
If you just need to have an authenticated user during testing the test cases you can use force_login which does not need any authentication properties just pass the user object.
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)
If anyone still following this , I think the attributes 'is_staff' and 'is_active' should be kept True for successfully logging in......
self.user = User.objects.create(username='testuser',password='pwd',is_active=1,is_staff=1)
This is my first test in Django - it's a simple view test to assert that the response in 200.
I'm using the authentication so I'm creating a test user in the first place:
class SettingsTests(TestCase):
def setUp(self):
self.client = Client()
self.username = 'test_user'
self.email = 'test#whatever.com'
self.password = 'test'
self.user = User.objects.create_user(self.username, self.email, self.password)
def tearDown(self):
self.user.delete()
Here's the actual test where I'm trying to test the view:
def test_settings_view_is_diplayed_correctly(self):
login = self.client.login(username = self.username, password = self.password)
response = self.client.get('/')
self.assertEqual(response.status_code, 200)
This always returns 404, however if I try the same in the shell it works and returns 200!
Can somebody please tell me what's wrong here?
The problem, as suggested by Alasdair was the view. I had a get_object_or_404 shortcut and did not provide the needed object. Hence, the view was returning 404 and the test was failing...
I'm new to Pytest. I want to test my views which require login (decorated with #login_required).
I have following test function:
def test_add_new_post(self, client, user):
login_user(user)
assert current_user == user
data = {
'title': 'This is test post',
'body': 'This is test body'
}
client.post(url_for('posts.add_new'), data=data)
assert Post.query.count() == 1
where the client is:
#pytest.fixture(scope='session')
def client(request, app):
return app.test_client()
The assert current_user == user returns True, but the client.post returns the login page, because the login_required redirects to a login page. Why is this happening and what is the correct way of doing this?
This worked for me (based on this comment):
def test_with_authenticated_user(app):
#app.login_manager.request_loader
def load_user_from_request(request):
return User.query.first()
# now you can call client.post or similar methods
perhaps not the most streamlined way, but: "It can be convenient to globally turn off authentication when unit testing. To enable this, if either of the application configuration variables LOGIN_DISABLED or TESTING is set to True, this decorator will be ignored." via https://pythonhosted.org/Flask-Security/api.html
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.
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')
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)
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:
create a user
self.pravesh = User.objects.create(
email='psj.aaabbb#gmail.com',
first_name='Pravesh',
last_name='aaabbb',
phone='5456165156',
phonecountrycode='91'
)
set password for the user
self.password = 'example password'
self.pravesh.set_password(self.password)
test_method:
create client
client.login(email=self.pravesh.email, password=self.password)
get token (in case of token auth)
token = Token.objects.create(user=self.pravesh)
pass login information
response = client.post(
reverse('account:post-data'),
data = json.dumps(self.data),
HTTP_AUTHORIZATION='Token {}'.format(token),
content_type = 'application/json'
)