Django: test failing on a view with #login_required - django

I'm trying to build a test for a view that's decorated with
#login_required, since I failed to make it work, I did a simple test
and still can't make it pass.
Here is the code for the simple test and the view:
def test_login(self):
user = self._create_new_user()
self.assertTrue(user.is_active)
login = self.client.login(username=user.username,
password=self.data['password1'])
self.failUnless(login, 'Could not log in')
response = self.client.get('/accounts/testlogin/')
self.assertEqual(response.status_code, 200)
#login_required
def testlogin(request):
print 'testlogin !! '
return HttpResponse('OK')
_create_new_user() is saving the user and there is a test inside that
method to see that is working.
The test fails in the response.status_code, returning 302 and the
response instance is of a HttpResponseRedirect, is redirecting it as
if not logged in.
Any clue? I'm missing something?
Regards
Esteban

This testcase works for me:
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.test.client import Client
import unittest
class LoginTestCase(unittest.TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user('john', 'lennon#thebeatles.com', 'johnpassword')
def testLogin(self):
self.client.login(username='john', password='johnpassword')
response = self.client.get(reverse('testlogin-view'))
self.assertEqual(response.status_code, 200)
I suggest you (if you don't use them already) to use the reverse() function and name your URLs. This way you are sure that you get always the right URL.

Here is the answer:
Python 2.6.5 made a change to the way
cookies are stored which is subtly
incompatible with the test client.
This problem has been fixed in the
1.1.X and trunk branches, but the fix hasn't yet made it into a formal
release.
If you are using 1.1.X and Python
2.6.5, you're going to have problems with any test activity involving
cookies. You either need to downgrade
Python, or use the 1.1.X branch rather
than the 1.1.1 release.
A 1.1.2 release (that will include the
fix for the problem you describe) will
be made at the same time that we
release 1.2 - hopefully, very very
soon.
Yours, Russ Magee %-)
http://groups.google.com/group/django-users/browse_frm/thread/617457f5d62366ae/05f0c01fff0b9e6d?hl=en&lnk=gst&q=2.6.5#05f0c01fff0b9e6d

OK I was to facing same problem #resto solved my problem.
creating user this way below, lets the test client get the user logged in and get the response other than redirect (302)
self.user = User.objects.create_user('john', 'lennon#thebeatles.com', 'johnpassword')

Related

Bypass decorator with mock in django test

I am trying to write a simple test however my views are decorated with nested user_passes_test statements. They check things like a stripe subscription and is_authenticated. I have found various posts such as this which address how to bypass a decorator with patch but I can't quite work out how to integrate everything together.
tests.py
#patch('dashboard.views.authorised_base_user_checks', lambda func: func)
def test_dashboard_root_exists(self):
response = self.client.get('/dashboard/')
self.assertEqual(200, response.status_code)
decorator in views
def authorised_base_user_checks(view_func):
decorated_view_func = login_required(user_active(subscriber_exists(subscriber_valid(view_func))))
return decorated_view_func
views.py
#authorised_base_user_checks
def IndexView(request):
...
The above still fails to pass through the decorator.
Thanks!
This approach with patching of decorator most probably does not work because import of views module happens after the patching. If view has been already imported the decorator had been already applied to IndexView and patching the decorator function would have no effect at all.
You can reload the view module to overcome this:
import imp
import dashboard.views
#patch('dashboard.views.authorised_base_user_checks', lambda func: func)
def test_dashboard_root_exists(self):
# reload module to make sure view is decorated with patched decorator
imp.reload(views)
response = self.client.get('/dashboard/')
self.assertEqual(200, response.status_code)
# reload again
patch.stopall()
imp.reload(views)
Disclaimer: this code only demonstrates the idea. You need to make sure stopall and final reload always happens, so they should be in finally or in tearDown.

Django: Stripe & POST request

I am currently trying to implement Stripe Connect in my Django project. Stripe documentations states for Standard accounts:
Assuming no error occurred, the last step is to use the provided code
to make a POST request to our access_token_url endpoint to fetch the
user’s Stripe credentials:
curl https://connect.stripe.com/oauth/token \
-d client_secret=sk_test_Dur3X2cOCwyjlf9Nr7OCf3qO \
-d code="{AUTHORIZATION_CODE}" \
-d grant_type=authorization_code
I now wonder how to send a POST request with Django without form & user action (clicking the submit button)?
Since Standard Connect relies on OAuth for its connection flow:
https://stripe.com/docs/connect/standard-accounts#oauth-flow
so you can use an OAuth python library like Rauth, as you mentioned, to handle the flow.
Also please note that Stripe Python library provides an implementation of the OAuth flow here:
https://github.com/stripe/stripe-python/blob/a938c352c4c11c1e6fee064d5ac6e49c590d9ca4/stripe/oauth.py
You can see an example of its usage here:
https://github.com/stripe/stripe-python/blob/f948b8b95b6df5b57c7444a05d6c83c8c5e6a0ac/examples/oauth.py
The example uses Flask not Django but should give you a good idea in terms of its use.
With regards to the advantages of using an existing OAuth implementation as opposed to implementing the calls directly yourself: one advantage I see is that your code would reuse a library that generally covers all different uses cases (e.g better error handling) and is also well tested.
Thanks to #psmvac I could implement it the 'proper' way now using the oAuth of Stripe. Here some reference/example Django code if anyone is trying the same. Obviously, urls.py has to be configured. This is in my views.py:
def stripe_authorize(request):
import stripe
stripe.api_key = ''
stripe.client_id = 'XYZ'
url = stripe.OAuth.authorize_url(scope='read_only')
return redirect(url)
def stripe_callback(request):
import stripe
from django.http import HttpResponse
# import requests
stripe.api_key = 'XYZ'
## import json # ?
code = request.GET.get('code', '')
try:
resp = stripe.OAuth.token(grant_type='authorization_code', code=code)
except stripe.oauth_error.OAuthError as e:
full_response = 'Error: ' + str(e)
return HttpResponse(full_response)
full_response = '''
<p>Success! Account <code>{stripe_user_id}</code> is connected.</p>
<p>Click here to
disconnect the account.</p>
'''.format(stripe_user_id=resp['stripe_user_id'])
return HttpResponse(full_response)
def stripe_deauthorize(request):
from django.http import HttpResponse
import stripe
stripe_user_id = request.GET.get('stripe_user_id', '')
try:
stripe.OAuth.deauthorize(stripe_user_id=stripe_user_id)
except stripe.oauth_error.OAuthError as e:
return 'Error: ' + str(e)
full_response = '''
<p>Success! Account <code>{stripe_user_id}</code> is disconnected.</p>
<p>Click here to restart the OAuth flow.</p>
'''.format(stripe_user_id=stripe_user_id)
return HttpResponse(full_response)

Django how to test if message is sent

I want to test if message is sent to user after submit. I'm using django.contrib.messages. Everything seems to be working during manual testing (runserver), but in unit test I don't get messages.
Code that stores message:
messages.success(request, _('Internationalized evil message.'))
Code that should test message:
from django.contrib.messages.api import get_messages
...
def test_message_should_sent_to_user(self):
"""After successful phone number submit, message should be displayed."""
response = self.client.post(
reverse('evil.views.evil_data_submit'), self.valid_data)
messages = get_messages(response.request)
self.assertNotEqual(len(messages), 0)
It looks like that no middleware is called during test client post method call.
Update after #Tisho answer
Messages should be found in response.context, even my guts say that it should work, but it doesn't. I've placed import pdb; pdb.set_trace() in django/contrib/messages/context_processors.py to see if its called during test client.post, its not.
I've double checked TEMPLATE_CONTEXT_PROCESSORS, MIDDLEWARE_CLASSES and INSTALLED_APPS - probably tomorrow I'll discover that I missed something.
Important detail
Forgot to mention that in case of successful submit view returns HttpResponseRedirect therefore response.context is empty.
Solution
View returns redirect (which has no context data), to solve that we can pass follow=True to client.post method (method suggested by #Tisho).
During unit tests, the message could be found in
response = self.client.post(
reverse('evil.views.evil_data_submit'), self.valid_data)
messages = response.context['messages']
If your view returns a redirect, response.context will be empty unless you pass follow=True, like so:
response = self.client.post(
reverse('evil.views.evil_data_submit'),
self.valid_data,
follow=True)
The best way I found is by using mock
http://www.voidspace.org.uk/python/mock/patch.html#patch-methods-start-and-stop
class SimpleCommentTestDirect(TestCase):
def setUp(self):
self._patcher1 = patch('django.contrib.messages.error')
self.mock_error = self._patcher1.start()
def tearDown(self):
self._patcher1.stop()
def test_error_message(self):
self.client.get('/vote/direct/unknownapp/comment/1/up/')
self.assertEqual(self.mock_error.call_args[0][1], 'Wrong request. Model.')
BTW: There are also should be a way to get request by using mock. And by using request object get message from django.contrib.messages.get_messages

django test client gets 404 for all urls

I am doing my first experiments with django testing and I am having the problem that I always get the 404 template regardless which url (even /) I am using.
If I throw the very same code into the django shell it's working as expected and always presents me the contents of the requested url.
class SimpleTest(TestCase):
def setUp(self):
self.user = User.objects.create_user('test', 'test', 'test')
self.user.is_staff = True
self.user.save()
self.client = Client()
def test_something(self):
self.assertTrue(self.client.login(username='test', password= 'test'))
self.client.get("/")
The login returns True, but the get() fails. Any hints what I am doing wrong here?
Keep in mind that most views use something like get_object_or_404, get_list_or_404, or simply raise Http404 when there's a problem accessing some object or another. You'll need to make sure that your test database is populated with sufficient objects to fulfill all these requirements to make the view not return a 404.
Remember, when running tests, the database is rolled back after each test (using transactions), so each test method must stand on its own or the setUp method must populate the database with any required dependencies.

How to use session in TestCase in Django?

I would like to read some session variables from a test (Django TestCase)
How to do that in a clean way ?
def test_add_docs(self):
"""
Test add docs
"""
# I would access to the session here:
self.request.session['documents_to_share_ids'] = [1]
response = self.client.get(reverse(self.document_add_view_id, args=[1]), follow=True)
self.assertEquals(response.status_code, 200)
As of Django 1.7+ this is much easier. Make sure you set the session as a variable instead of referencing it directly.
def test_something(self):
session = self.client.session
session['somekey'] = 'test'
session.save()
andreaspelme's workaround is only needed in older versions of django. See docs
Unfortunately, this is not a easy as you would hope for at the moment. As you might have noticed, just using self.client.session directly will not work if you have not called other views that has set up the sessions with appropriate session cookies for you. The session store/cookie must then be set up manually, or via other views.
There is an open ticket to make it easier to mock sessions with the test client: https://code.djangoproject.com/ticket/10899
In addition to the workaround in the ticket, there is a trick that can be used if you are using django.contrib.auth. The test clients login() method sets up a session store/cookie that can be used later in the test.
If you have any other views that sets sessions, requesting them will do the trick too (you probably have another view that sets sessions, otherwise your view that reads the sessions will be pretty unusable).
from django.test import TestCase
from django.contrib.auth.models import User
class YourTest(TestCase):
def test_add_docs(self):
# If you already have another user, you might want to use it instead
User.objects.create_superuser('admin', 'foo#foo.com', 'admin')
# self.client.login sets up self.client.session to be usable
self.client.login(username='admin', password='admin')
session = self.client.session
session['documents_to_share_ids'] = [1]
session.save()
response = self.client.get('/') # request.session['documents_to_share_ids'] will be available
If you need to initialize a session for the request in tests to manipulate it directly:
from django.contrib.sessions.middleware import SessionMiddleware
from django.http import HttpRequest
request = HttpRequest()
middleware = SessionMiddleware()
middleware.process_request(request)
request.session.save()
If testing my django project with pytest, I can not see any modifications to the session that are made in the view. (That is because the Sessions middleware doesn't get called.)
I found the following approach to be useful:
from unittest.mock import patch
from django.test import Client
from django.contrib.sessions.backends import db
def test_client_with_session():
client = Client()
session = {} # the session object that will persist throughout the test
with patch.object(db, "SessionStore", return_value=session):
client.post('/url-that-sets-session-key/')
assert session['some_key_set_by_the_view']
client.get('/url-that-reads-session-key/')
This approach has the benefit of not requiring database access.
You should be able to access a Client's session variales through its session property, so I guess self.client.session['documents_to_share_ids'] = [1] should be what you are looking for!