Django Testing
Table of Contents
During my Final Year Project, I had many chances to work React and Django. In this blog post, I will be blogging about how to test a Django application.
Introduction #
Testing is an important part of software development. It is a way to ensure that the code is working as expected. There are many types of testing, such as unit testing, integration testing, and end-to-end testing. In this blog post, I will be focusing on unit testing and integration testing.
In this blog, I will be using Django as an example. Django is a Python web framework. It is a great framework to build web applications. Django has a built-in testing framework. It is a great way to test your Django application.
Example code will also be provided in the examples below.
Running Tests in Django #
To run tests in Django, navigate to the folder with the manage.py
file. Then run the following command:
python manage.py test
This will run the built in library (unittest) to run all the tests in the tests.py
file. If you want to run a specific test, you can run the following command:
python manage.py test <app_name>.tests.<test_name>
You can even run tests based on patterns using:
python manage.py test --pattern="pattern"
By default, Django runs files which follow the pattern test*.py
.
Unit Testing #
Unit testing is a way to test a single unit of code. For example, if you have a function that adds two numbers together, you can write a unit test to test that function. The unit test will check if the function returns the correct result. If the function returns the wrong result, the unit test will fail. This is a good way to ensure that the function is working as expected.
Integration Testing #
Integration testing is a way to test the integration between different parts of the code. For example, if there are 2 functions in Django that work together, you can write an integration test to test the integration between the 2 functions.
Basic testing in Django #
from django.test import TestCase
class Test(TestCase):
def setUp(self):
"""The setup function that is executed before every test in this class"""
...
def test_a(self):
"""Test function"""
...
def tearDown(self):
"""The teardown function that is executed after every test in this class"""
...
Before running test_a
, the setUp
function will be executed. This is a good place to set up the test environment.
For example, you can create a user and log them in. Then you can use the logged in user to test the code.
Similar to setUp
, the tearDown
function will be executed after test_a
.
This is a good place to clean up the test environment.
This also includes tests for Models
, Views
and AdminSites
.
Tools #
In Django, there are many tools that can be used to test the code.
- Client - This is a way to test the code by sending HTTP requests to the server.
- TransactionTestCase - This is a way to test the code by using a database transaction.
- LiveServerTestCase - This is a way to test the code by using a live server.
- Assertions - This is a way to check if the code is working as expected.
- Tagging - This is a way to tag tests to be used when running tests.
- Async Testing - This is a way to test the code by using asynchronous code.
- coverage.py - To check for coverage
Client #
The client is a way to test the code by sending HTTP requests to the server.
from django.test import TestCase, Client
class Test(TestCase):
def setUp(self):
"""The setup function that is executed before every test in this class"""
self.client = Client()
def test_a(self):
"""Test function"""
response = self.client.get('/path/to/page')
...
By using self.client
, you can make a request to the Django code that is being tested.
The responses can be used to check if the code is working as expected.
TransactionTestCase #
The TransactionTestCase is a way to test the code by using a database transaction. It provides the following additional features
- Rollback the database after every test (By truncating all tables)
- Add database fixtures and specialized asserts
This is usually used to test database specific functionalities. Normal test cases makes use of transactions which are rolled back to the initial state of the database after every test. This method of rolling back might affect some database related tests like atomic transactions.
LiveServerTestCase #
The LiveServerTestCase is a way to test the code by using a live server. It provides the following additional features
- A Live server running on port 8081 (By default)
This can be used in conjunction with Selenium to test the code.
Assertions #
Django provides a set of assertions that can be used to check if the code is working as expected. In the example below, I will be showing some of the different assertions that can be used in Django.
from django.test import TestCase
class Test(TestCase):
def test_a(self):
"""Test function"""
self.assertEqual(1, 1)
self.assertNotEqual(1, 2)
self.assertTrue(True)
self.assertFalse(False)
self.assertIsNone(None)
self.assertIsNotNone(1)
self.assertIn(1, [1, 2, 3])
self.assertNotIn(4, [1, 2, 3])
self.assertIsInstance(1, int)
self.assertNotIsInstance(1, str)
self.assertGreater(2, 1)
self.assertGreaterEqual(2, 1)
self.assertLess(1, 2)
self.assertLessEqual(1, 2)
self.assertRegex('abc', 'a')
self.assertNotRegex('abc', 'd')
self.assertCountEqual([1, 2, 3], [3, 2, 1])
self.assertQuerysetEqual([1, 2, 3], [3, 2, 1])
self.assertTemplateUsed('template.html')
self.assertTemplateNotUsed('template.html')
self.assertRedirects(response, '/path/to/page')
self.assertFormError(response, 'form', 'field', 'error')
self.assertContains(response, 'text')
self.assertNotContains(response, 'text')
self.assertJSONEqual(response, {'key': 'value'})
self.assertJSONNotEqual(response, {'key': 'value'})
self.assertContains(response, 'text', status_code=200)
self.assertContains(response, 'text', html=True)
self.assertContains(response, 'text', count=1)
self.assertContains(response, 'text', msg_prefix='msg')
self.assertContains(response, 'text', status_code=200, html=True, count=1, msg_prefix='msg')
Tagging #
Django provides a way to tag tests to be used when running tests. This is useful when you want to run a specific set of tests based on tags.
from django.test import TestCase, tag
class Test(TestCase):
@tag('tag1', 'tag2')
def test_a(self):
"""Test function"""
...
@tag('tag1')
def test_b(self):
"""Test function"""
...
@tag('tag2')
def test_c(self):
"""Test function"""
...
In the example above, test_a
has 2 tags, tag1
and tag2
, test_b
has 1 tag, tag1
, test_c
has 1 tag, tag2
.
To run tests with a specific tag, you can use the following command.
python manage.py test --tag tag1
Async Testing #
Django provides a way to test the code by using asynchronous code. This is useful when you want to test the code that uses asynchronous code.
from django.test import TestCase, AsyncClient
class Test(TestCase):
async def test_a(self):
"""Test function"""
async with AsyncClient() as client:
response = await client.get('/path/to/page')
...
coverage.py
#
coverage.py
is a tool that can be used to check for coverage.
This is useful when you want to check if the code is covered by tests.
It also shows the lines that are not covered, percentage of coverage for files. It also supports HTML reports to be viewed in a browser.
Testing different parts #
Testing APIs #
To test APIs, you can make use of the Client
class together with the Assertions
class.
from django.test import TestCase, Client
class Test(TestCase):
def setUp(self):
"""The setup function that is executed before every test in this class"""
self.client = Client()
def test_a(self):
"""Test function"""
response = self.client.get('/path/to/page')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), {'key': 'value'})
Testing Admin Sites #
Testing admin sites are a little different from testing APIs. To test admin sites, you can make use of the template code below.
from django.contrib.admin.sites import AdminSite
class Test(TestCase):
def setUp(self):
"""The setup function that is executed before every test in this class"""
self.site = AdminSite()
def test_user_admin_chart_active_user_endpoint(self):
"""Test if the active user endpoint is working correctly"""
user_admin = CustomUserAdmin(User, self.site) # Initialize the custom admin class
result = user_admin.custom_admin_page_api()
...
Conclusion #
In this article, I have shown some of the tools that can be used to test the code in Django. I hope this article was helpful to you to bring your Django tests to the next level. Please let me know if you have any questions or suggestions.