Django provides many testing tools; in this tutorial we'll use some of them to test an app with a custom user model and authentication.
We'll test the app userauth
that we have created in an earlier tutorial. It features a custom user model and authentication with allauth. The command to run tests for a specific app or directory is:
python3 manage.py test userauth
The argument test
is comparable to other arguments that Django uses, such as runserver
, createsuperuser
, migrate
etc. You might get the following error:
Got an error creating the test database: permission denied to create database
That's because Django will create a test database, that will be destroyed when all the tests have been executed. So you need to give the database user the permission to create this test database. Go to the psql
command prompt:
psql postgres
and type in the following command:
ALTER USER usr_pet CREATEDB;
where usr_pet
is your database user. Leave psql
with \q
.
Another error that might occur is:
ValueError: Missing staticfiles manifest entry for 'images/favicon-32x32.png'
or something similar. This is due to the use of ManifestStaticFilesStorage
in the parameter STATICFILES_STORAGE
in our settings. Django recommends to set this parameter to its default during testing, so comment out the line in your settings where this parameter is defined.
Now if we run python3 manage.py test userauth
, Django will tell you how many tests were executed and display any errors. Even with no tests we can still run the test command; Django will go through setup and do some more things, so will return OK or detect errors if it found any.
There are many ways of testing and many articles on the limitations of it. Two popular testing methods are coverage testing (e.g. with Coverage) and browser automation (e.g. with Selenium). Here we will limit ourselves to coverage testing, which can be easily integrated with Django. Install the package:
pip3 install coverage
Add it to requirements.txt
and add coverage
to INSTALLED_APPS
. Run it with:
coverage run --source=userauth manage.py test userauth
The flag source
tells coverage only to measure the code in the directory userauth
, and the userauth
and the end of the command tells Django only to execute tests in the app userauth
. The subsequent command
coverage html
creates a directory htmlcov
in the project directory with a file index.html
in it. Right click it and choose open in browser.
For our app userauth
we see that a substantial portion of the code is already visited, even without tests. That's because in our app we have made maximum use of Django's and allauth's built-in classes and methods. Let's create some tests for the pieces of code that were not covered, starting with a simple one: the string representation of the custom user model. Recapping from our earlier tutorial on our customer user model:
def __str__(self):
return f"{self.username}: {self.first_name} {self.last_name}"
We will use Django's class Testcase
. First we create a user in our set up with setUpTestData
, then we test with assertEqual
whether the string representation is what it should be:
from .models import CustomUser
from django.test import TestCase
class TestCustomUser(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = CustomUser.objects.create(username="userJohnDoe", password="secretpassword", first_name="John", last_name="Doe")
def test_string_representation_of_customuser(self):
expected_representation_customuser = "userJohnDoe: John Doe"
self.assertEqual(expected_representation_customuser, str(self.user))
Running coverage again will reveal that we have now covered that piece of code. This is of course a very simple example. Let's test the get_absolute_url
method of our model. Again from our earlier tutorial:
def get_absolute_url(self):
return reverse('account_profile')
with the following in our urls.py
:
path('profile/', profile_view, name='account_profile'),
and in our views.py
:
def profile_view(request):
return render(request, 'account/profile.html')
A call to profile_view
with a logged in user should give a valid response. The Django docs describe how to simulate a user login using RequestFactory
, which generates a request
object. We import RequestFactory
and profile_view
:
from .views import profile_view
from django.test import RequestFactory
In our setUpTestData
we add the line:
cls.factory = RequestFactory()
and then our test is (using the HttpResponse
attribute status_code
):
def test_profile_view_with_user_gets_valid_response(self):
request = self.factory.get(self.user.get_absolute_url())
# log user in
request.user = self.user
self.assertEqual(profile_view(request).status_code, 200)
Our final test for userauth
will be on LoginForm
in forms.py
. Define a second user in setUpTestData
:
cls.user2 = CustomUser.objects.create(username="undefined", password="undefined", first_name="undefined", last_name="undefined")
We cannot reuse the first user, because the tests are not necessarily run in the order they are defined, which means that changes in one test could affect reused variables in another. Define some form data, feed it to SignupForm
, and use the form's signup
method on the newly created user to check whether the method does what it's supposed to do:
def test_signup_form(self):
form_data = {'first_name': "Jane", 'last_name': "Doe", 'display_name': "Jane Doe"}
form = SignupForm(data=form_data)
self.assertTrue(form.is_valid())
form.signup(self, user=self.user2)
self.assertEqual(self.user2.display_name, "Jane Doe")
Running coverage again shows that we have covered more than 95% of the code of userauth
and that only some straightforward code lines are missing. We leave the testing of this app and move on to the next.
Comment on this article (sign in first or confirm by name and email below)