Tests for Your Django Apps: How to Write Them Effectively (A Complete Guide)

Discover how to use the TestCase class, the test client, the assert methods, and the fixtures to boost your Django testing skills.

Tests for Your Django Apps: How to Write Them Effectively (A Complete Guide)

Testing is an essential part of any software development process. It helps to ensure that the code is working as expected, to catch bugs and errors before they affect the users, and to improve the quality and maintainability of the code. In this article, I will show you how to test your Django applications using the built-in testing framework and some useful tools and libraries.

Django's testing framework

Django provides a testing framework that is based on Python's unittest module but with some extra features and conveniences. The testing framework allows you to write different types of tests, such as:

  • Unit tests: These are tests that check the functionality of a single component, such as a model, a view, or a form.

  • Integration tests: These are tests that check how different components work together, such as how a view interacts with a model or a template.

  • Functional tests: These are tests that check the behaviour of the application from the user's perspective, such as how a web page looks or how a form submits data.

To write tests for your Django application, you need to create a file called tests.py in each app directory, or a subdirectory called tests with multiple files. You can also use a custom test runner to change the default behaviour of the testing framework.

To run the tests, you can use the manage.py test command, which will discover and execute all the tests in your project. You can also specify an app name, a test case class, or a test method name to run a subset of tests.

Writing a simple unit test

Let's start by writing a simple unit test for a model. Suppose we have an app called blog with a model called Post that has fields for title, content, author, and published date. We want to test that the model has a _str_ method that returns the title of the post.

To write a unit test, we need to subclass django.test.TestCase and define one or more methods that start with test_. In each method, we can use the self.assert* methods to make assertions about the expected outcomes.

Here is an example of a unit test for the Post model:

from django.test import TestCase
from django.contrib.auth.models import User
from blog.models import Post

class PostModelTest(TestCase):
    def setUp(self):
        # Create a user and a post for testing
        self.user = User.objects.create_user(username='testuser', password='testpass')
        self.post = Post.objects.create(title='Test Post', content='This is a test post', author=self.user)

    def test_post_str(self):
        # Test that the _str_ method returns the title of the post
        self.assertEqual(str(self.post), 'Test Post')

To run this test, we can use the command manage.py test blog.tests.PostModelTest.

Writing an integration test

Next, let's write an integration test for a view. Suppose we have a view called post_detail that displays the details of a single post. We want to test that the view returns a 200 status code, uses the correct template, and contains the post title in the HTML.

To write an integration test, we can use the self.client attribute, which is an instance of django.test.Client. This is a dummy web browser that we can use to make requests to our views and inspect the responses.

Here is an example of an integration test for the post_detail view:

from django.test import TestCase
from django.urls import reverse
from blog.models import Post

class PostDetailViewTest(TestCase):
    def setUp(self):
        # Create a post for testing
        self.post = Post.objects.create(title='Test Post', content='This is a test post')

    def test_post_detail_view(self):
        # Test that the post detail view returns a 200 status code,
        # uses the correct template, and contains the post title
        response = self.client.get(reverse('post_detail', args=[self.post.id]))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'blog/post_detail.html')
        self.assertContains(response, 'Test Post')

To run this test, we can use the command manage.py test blog.tests.PostDetailViewTest.

Writing a functional test

Finally, let's write a functional test for our application. Suppose we want to test that a user can create a new post using a form. We want to test that the user can access the form page, fill in the fields, submit the form, and see the new post on the list page.

To write a functional test, we can use a tool called Selenium WebDriver, which allows us to automate browser actions and interactions. To use Selenium WebDriver with Django's testing framework, we need to install the selenium package and a browser driver, such as ChromeDriver or FirefoxDriver.

Here is an example of a functional test for creating a new post:

from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

class NewPostTest(LiveServerTestCase):
    def setUp(self):
        # Create a browser instance
        self.browser = webdriver.Chrome()

    def tearDown(self):
        # Close the browser
        self.browser.quit()

    def test_create_new_post(self):
        # Test that a user can create a new post using a form

        # The user goes to the home page of the blog
        self.browser.get(self.live_server_url)

        # The user sees a link to create a new post and clicks on it
        new_post_link = self.browser.find_element_by_link_text('New post')
        new_post_link.click()

        # The user sees a form to enter the title and content of the post
        title_input = self.browser.find_element_by_id('id_title')
        content_input = self.browser.find_element_by_id('id_content')

        # The user types in the title and content of the post
        title_input.send_keys('My first post')
        content_input.send_keys('This is my first post using Selenium')

        # The user submits the form
        content_input.send_keys(Keys.ENTER)

        # The user sees the new post on the list page
        self.browser.get(self.live_server_url)
        new_post = self.browser.find_element_by_link_text('My first post')
        self.assertIsNotNone(new_post)

To run this test, we can use the command manage.py test blog.tests.NewPostTest.

Conclusion

In this article, I have shown you how to test your Django applications using the built-in testing framework and some useful tools and libraries. I hope you have learned something new and useful from this article.

If you want to learn more about testing in Django, you should check out my article, How to Test APIs, Authentication and Permissions in Django. You will learn how to use the Django REST Framework, the APIClient class, the APITestCase class, and other tools and libraries to write tests for your RESTful web services.

Happy testing!