I am developing a web application using Django 1.6 where I require users to log in using my login form. I want to write a test case that tests the procedure of a user logging in.
I did succeed to get a working login page, meaning I was able to log in. The following steps explain my setup. Writing a test case for this procedure did however fail. At this point I'm neither able to spot an error in my test, nor sure whether this approach makes sense before all. Can someone help me?
Update: In the meantime I have noticed, that my form error message was too unspecific. The issue appears to be caused by empty form fields that fail some form validation step. When I add the errors {{ form.errors }}
to the template, the response contains the following (formatted a little less nicely):
<ul class="errorlist">
<li>username
<ul class="errorlist">
<li>This field is required.</li>
</ul>
</li>
<li>password
<ul class="errorlist">
<li>This field is required.</li>
</ul>
</li>
</ul>
I'm still not sure how to test this procedure most reasonably. I'm quite new to Django and the philosophy of it's testing framework. May it be, that this issue falls not directly into the scope of the Django testing framework, where models, and views, and forms are (seemingly) intended to be tested rather seperated from each other? Does this test rather fall into the realm of an independent test suite doing black-box/functional testing, e.g., using Selenium?
I started like this:
Started a fresh project, like this:
django-admin.py startproject login_test
added an entry to the login_test/urls.py
, like this:
urlpatterns = patterns('',
url(r'^login/$', 'django.contrib.auth.views.login'),
url(r'^admin/', include(admin.site.urls)),
)
made up a template at /home/moooeeeep/login_test/templates/registration/login.html
, like this:
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
{% if not user.is_active %}
<form method="post" action="{% url 'django.contrib.auth.views.login' %}">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Login</button>
<input type="hidden" name="next" value="{% url 'django.contrib.auth.views.login' %}" />
</form>
{% else %}
<p>You are logged in as <strong>{{ user.username }}</strong>.</p>
{% endif %}
added the template directory to the TEMPLATE_DIRS
in the login_test/settings.py
, like this:
TEMPLATE_DIRS = (
'/home/moooeeeep/login_test/templates/',
)
Sync'ed the database to get the auth-related tables, and added a superuser like this:
python manage.py syncdb
If I did not miss a step, at this point I am able to log into my site. When logged in I get displayed my username, if not I see the form, if login fails it displays an error message in addition.
My test case however, after submitting correct post data, gets to see the failed login response, with the user not being logged in.
Here's my login_test/tests.py
:
from django.contrib.auth.models import User
from django.contrib.auth import SESSION_KEY
from django.test import TestCase
class LogInTest(TestCase):
def setUp(self):
self.credentials = {
'username': 'testuser',
'password': 'secret'}
User.objects.create_user(**self.credentials)
def test_login(self):
# login
response = self.client.post('/login/', **self.credentials)
# should be logged in now, fails however
self.assertTrue(response.context['user'].is_active)
Here's the output:
$ python manage.py test
Creating test database for alias 'default'...
F
======================================================================
FAIL: test_login (login_test.tests.LogInTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/moooeeeep/login_test/login_test/tests.py", line 16, in test_login
self.assertTrue(response.context['user'].is_active)
AssertionError: False is not true
----------------------------------------------------------------------
Ran 1 test in 0.008s
FAILED (failures=1)
Destroying test database for alias 'default'...
Here's my login_test/settings.py
:
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '+6e-6t8dnl$75fx%=7y$+4ipo&i=kvw(p*963p37)7o$1-)30u'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
TEMPLATE_DEBUG = True
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
ROOT_URLCONF = 'login_test.urls'
WSGI_APPLICATION = 'login_test.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
TEMPLATE_DIRS = (
'/home/moooeeeep/login_test/templates/',
)
I found out that my passing of the post parameters was wrong (the dictionary must not be unpacked). I also needed to learn, that I needed to follow the redirect issued by the post request after sending the data. Then my login test worked (although the page that was target of the redirection does not exist yet).
Here's my updated test case:
from django.contrib.auth.models import User
from django.test import TestCase
class LogInTest(TestCase):
def setUp(self):
self.credentials = {
'username': 'testuser',
'password': 'secret'}
User.objects.create_user(**self.credentials)
def test_login(self):
# send login data
response = self.client.post('/login/', self.credentials, follow=True)
# should be logged in now
self.assertTrue(response.context['user'].is_active)
Here's the output:
$ python manage.py test
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.196s
OK
Destroying test database for alias 'default'...