Unit Testing in Django
- Digital Engineering
- General
Unit Testing in Django
Testing is a essential step for software development process. Many developers ignore this step and ended up finding bugs in production.
Manual testing is fine until your application grows on a large scale. Once your application is big you cant test each and every component of your application whenever you make a change or introduce new features. That’s where unit testing comes in picture.
Unit testing is an automated way of testing your application. You write tests for all the units/components you want to test and it will do its job.
How does Unit Testing in Django work?
When we create our django app using python manage.py createapp app_name it creates a tests.py in our app directory. This file is used by django to run all unit tests for models and other components within the app. In test file we inherit django’s test class TestCase or DRF’s test class APITestCase and write our own test functions. These function names start with name ‘test_’ . setUp(self) and tearDown(self) are used to setup required environment and clean up after running test. Django uses a separate database from whatever database we access in out development and destroys it after running all tests.
Lets create unit test for APIs
Prerequisites
– Virtual Environment setup for a Django project.
– Django rest framework setup
– Basic API endpoints created
Files Structure in Django:-
Django gives tests.py file in app, but if our app big in size and components then we have to write tests in multiple files to keep everything clean and sorted. For that we have to create directory with name tests in app directory and create all test file start with name “test_”.
Now lets see a basic unit test for login api…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
from django.urls import reverse from rest_framework.test import APITestCase, APIClient from django.contrib.auth.models import User from rest_framework.authtoken.models import Token def create_user(self): self.client = APIClient() self.user = User(first_name='test_user',last_name='test',email= 'test@gmail.com',username='test',password="abc123") self.user.set_password("abc123") self.user.save() self.token = Token.objects.create(user=self.user) class UserLoginTestCase(APITestCase): @classmethod def setUp(self): # SetUp required environment for tests create_user(self) self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token.key) def test_user_login(self): data = {'username':'test','password':"abc123"} login_url = reverse('login') response = self.client.post(login_url,data) resp = response.json() self.assertEqual(response.status_code,200) def tearDown(self): # Clean up after each test self.user.delete() |
Here we are testing login api functionality. Unit test start testing a function in isolation thats why we have create required attributes to run a login api. Here user needs to be there in order to login so we have created a user in our setUp function. If every information is given correct like credentials, authentication token, api url then test will run successfully. A self.assertSomething() verifies values passes through arguments are correctly matching. These values are nothing but out expected outputs from api response. We can call multiple assert functions, if all functions returns true as boolean then test is identified as successful otherwise failed.
1 2 3 4 5 6 7 8 |
def test_user_login(self): data = {'username':'test','password':"abc123"} login_url = reverse('login') response = self.client.post(login_url,data) resp = response.json() self.assertEqual(response.status_code,200) self.assertEqual(resp['message'],"Login Successful !!") |
We have multiple type of Assert Functions:-
Test Database for unit tests
When we run our tests by default Django create a database and destroys it after finalization. This is to avoid conflicts between our production and/or development database. We can define and customize a separate database for tests specifically in DATABASE dictionary in settings.py file something like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
DATABASES = { 'default': { 'ENGINE': '<engine>', 'NAME': '<database name>', 'USER': '<database user>', ... }, ... 'TEST': { # testing database customization here 'NAME': '<your testing database name>', # some other customization, for example the user user, password, etc }, } |
Running Your Tests
python manage.py test will search all your tests files and execute them and will show success and error for each testcase.
We can customize this command to run some specific test cases and avoid running all the test cases
- python manage.py test app_name runs tests only of specific app
- python manage.py test app_name.test_file_name runs tests only for given file
- python manage.py test app_name.test_file_name.ClassName runs tests only for given class
- python manage.py test app_name.test_file_name.ClassName.test_function runs only given test function
Making Unit test Faster
As long as we are testing our mid size application with limited test cases running tests in basic way works fine (using command python manage.py test) and speed of testing doesn’t bother us. But as our application grows on large scale running all the test in one go becomes slow. That is because major time takes in creating database running all our migrations and running all our testcases one by one. We can reduce significant amount of time by using –keepdb and –parallel. As unit test doesn’t depends on each other so we can run them in parallel. Keepdb will avoid erasing database between each test. We can use these feature easily by using:
python manage.py test -keepdb –parallel
Its not mandatory to use them together.
This is it for this blog, if you have any doubts we can discuss them in comment section.
Recommended links
https://docs.djangoproject.com/en/3.2/topics/testing/overview/
https://www.django-rest-framework.org/api-guide/testing/
Related content
Auriga: Leveling Up for Enterprise Growth!
Auriga’s journey began in 2010 crafting products for India’s