Testing a custom user model with Django's testing tools and coverage

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.

July 13, 2020, 1:11 p.m.
Themes: Custom User Model Authentication Testing

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:


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):

    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)
    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)