How to write tests in Django: Best practices for testing in Django

·9 min read

How to write tests in Django: Best practices for testing in Django

Writing tests for Django applications

Is your application running as expected or are you on the hunt for relevant solutions to test your application’s output? This might help!

Brief: With the help of the test execution framework and miscellaneous utilities of Django, you can simulate requests, insert test data, analyze your application’s output, and lastly double check your code.

Let’s get started from scratch!

 

Why is Testing needed at all?

When you write new code, you are very likely to encounter errors - be it expected or not. Testing a system that you have coded makes it reliable and effective which consequently improves the quality of your code. The more the test cases are designed and implemented for a system, the higher is the quality. However, testing a web application can be demanding at times as it is made of a number of layers ranging from HTTP level request handling to validating and processing forms and rendering templates.

Using Django’s test execution framework and assorted services, you can simulate requests, insert test data for the input of test cases, analyze and understand your application’s output and then you can modify your code accordingly. But, how can we test an application? There are many ways to test software like Unit testing, Integration testing, Regression testing, Smoke testing, Alpha testing, Beta testing, and so on.

 

Testing tools for a Django application

Django offers a hierarchical test framework called the unittest library. Regardless of its name, it can also be used for integration testing. The framework adds API and tools for testing web and Django-specific behavior of an application. Django also offers an API called LiveServerTestCase and tools for using various frameworks for testing.

 

How to write a test in Django

To write a test, we can import any test base class from the django.test module. There are various test base classes like SimpleTestCase, TransactionTestCase, TestCase, LiveServerTestCase, etc. Then, you can write specific methods by using assert(firstArg, secondArg) assertion methods, explained below, to check whether your application functions execute as expected. Let’s go through the following very simple and straightforward example-

from django.test import TestCase


def concate_two_strings(str1, str2):
	return str1 + str2


class TestExample(TestCase):

	def test_concate_two_strings(self):
    	self.assertEqual(concate_two_strings("Hello, ", "How are you?"), "Hello, How are you?")

In the above code,

  • We imported TestCase base class for testing. You can use any base class as per your utility.
  • We defined a method named concate_two_strings which is our target method for testing.
  • We created a derived class for TestCase and defined a testing method named test_concate_two_strings. The testing methods use special assertion methods that return either True or False that indicates if the test has passed or failed respectively.

The above code will pass the test for checking the concatenation of two strings because the result of the target method is as expected.

 

Types of Testing in Django

There is mainly two types of testing in Django:

  • Unit testing - Individual testing that focuses on testing methods in an isolated manner.
  • Integration testing - User behavioral testing that focuses on testing an entire application as a whole.

In this article, we are going to focus and explore more about Unit testing.

 

Assertion

Assertions are the essential methods that determine whether a test has passed or failed by returning True or False. You can use multiple assertions in one test.

Let’s see this example,

from django.tests import TestCase

def get_max(num1, num2):
      return num1 if num1>=num2 else num2

class TestExample(TestCase):

      def test_get_max(self):
            self.assertEqual(get_max(4,7),7)

We have taken a very straightforward method get_max that returns the maximum number from the two numbers num1 and num2. The test_get_max method above in the TestExample class checks if the output of the given method matches the expected output. Here, the assertEqual method will return True because the get_max method will return the expected number that is 7 (second parameter of the assertEqual method).

 

Code References

Folder Structure

In this article, we will refer to the application 'ecommerce' and its modules and files for illustrations. This is the ideal structure to follow for your Django application.

└── ecommerce/
    ├── ecommerce/
    │   ├── wsgi.py
    │   ├── settings.py
    │   ├── urls.py
    │   ├── model.py
    │   └── tests/
    │       ├── test_urls.py
    │       └── test_model.py
    ├── store/
    │   ├── admin.py
    │   ├── views.py
    │   ├── urls.py
    │   ├── model.py
    │   └── tests/
    │       ├── test_views.py
    │       ├── test_model.py
    │       └── test_urls.py
    ├── customer/
    │   ├── admin.py
    │   ├── views.py
    │   ├── urls.py
    │   ├── model.py
    │   └── tests/
    │       ├── test_views.py
    │       ├── test_model.py
    │       └── test_urls.py
    └── manage.py

It is a good practice to create a folder called 'Tests' in your app folders and then name your test files with test_{name}.py format.

 

Project URLs

from django.contrib import admin
from django.urls import path,include
from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('',include('store.urls'))
]

urlpatterns+=static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)

 

App URLs

from django.urls import path
from . import views

urlpatterns=[
    path('',views.store,name='store'),
    path('cart/',views.cart,name='cart'),
    path('checkout/',views.checkout,name='checkout'),

    path('update_item/',views.update_item,name='update_item'),
    path('process_order/',views.processOrder,name='process_order'),

]

 

Unit Testing

Unit testing in Django focuses on each method individually. It uses a standard python library module named unittest that determines tests using a class-based approach. There are several subtests of Unit testing such as URL testing, Views testing, Model testing, Forms testing, API testing, etc. In Django, we can utilize these tests independently.

 

URL Testing

There are two ways we can test URLs in our application

  • Using Client instance

The test client is a class of Python that behaves as a dummy user or web browser, allowing you to test your URLs and views and interact with your Django application functionalities.

from django.test import TestCase

class UrlTest(TestCase):

    def testHomePage(self):
        response = self.client.get('/')
        print(response)

        self.assertEqual(response.status_code, 200)

In the above code, we defined the test method testHomePage, in which we got the HTTP response object by hitting the GET '/' URL. If the page would be retrieved, then the response object will contain the body of the page and its metadata like title, status code, etc. By executing the assertEqual method, we are simply checking if the status code of the response is 200 or not, meaning if the page/URL that we had hit with the client object was found or not. If it wasn’t found, the status code would be 404 and the test method would return False.

  • Using Reverse and Resolve methods
from django.test import TestCase
from django.urls import reverse, resolve
from store.views import cart

class UrlTest(TestCase):

	def testCartPage(self):

    	url = reverse('cart')
    	print("Resolve : ", resolve(url))

    	self.assertEquals(resolve(url).func, cart)

In the above code, the assertEquals method checks if the resolver method matches the cart view. This test will pass because the result is as expected.

Django Tests Terminal Output - Passed 2 Tests

 

Now, let’s try the erroneous code.

from django.test import TestCase
from django.urls import reverse, resolve

class UrlTest(TestCase):

    def testCartPage(self):

        url = reverse('cart')
        print("Resolve : ", resolve(url))

        self.assertEquals(resolve(url).func, ‘cart’)

The assertEquals method in the above Testing class will fail because the resolver match returns the cart view and not the string 'cart' which is the second argument of the given assertion method.

Django Tests Terminal Output - Passed 1 Test, Failed 1 Test

 

Model Testing

Let’s understand the following code

class Product(models.Model):
    name = models.CharField(max_length=200,null=True)
    price = models.FloatField()
    digital = models.BooleanField(default=False,null=True,blank=True)
    image = models.ImageField(null=True,blank=True)

    def __str__(self):
        return self.name

    @property
    def imageURL(self):
        try:
            url = self.image.url
        except:
            url=''

        return url

In the above code, we have defined a model called Product with properties such as name, price, digital, image, and imageURL.

 

from django.test import TestCase
from store.models import Product

class ModelTest(TestCase):

    def testProductModel(self):
        product = Product.objects.create(name="ToyCar", price=800)
        self.assertEquals(str(product), 'ToyCar')
        print("IsInstance : ",isinstance(product,Product))
        self.assertTrue(isinstance(product,Product))

Now, we have defined the Testing class named ModelTest with a method called testProductModel. In that, a product instance is created with the name 'ToyCar' which we later checked with the assertEquals method which will return True. Then, we checked if the product instance is the instance of the Product class, which it is so it will return True and the test will pass.

Django Tests Terminal Output - Passed 3 Tests

 

Now, let’s try testing the same code but with a different product name (an error).

from django.test import TestCase
from store.models import Product

class ModelTest(TestCase):

    def testProductModel(self):
        product = Product.objects.create(name="ToyCar", price=800)
        self.assertEquals(str(product), 'ToyCar11')
        print("IsInstance : ",isinstance(product,Product))
        self.assertTrue(isinstance(product,Product))

In the above code, the assertEquals method will return False as the product name 'ToyCar' does not match with 'ToyCar11' and therefore, the test will fail.

Django Tests Terminal Output - Passed 2 Tests, Failed 1 Test

 

Executing Tests

Once you are done writing the tests for your application, you can run them by executing the following test commands written in the manage.py utility.

python manage.py test

 

To run the tests specifically within an app module (for 'store' in our case),

python manage.py test store

 

Run only one test case,

python manage.py test store.tests.test_urls.py

 

Variants of Assertion available in Django

assertNotEqual

assertNotEqual(firstArg, secondArg, msg=None)

Checks that the firstArg and secondArg are not equal and so returns True otherwise False (meaning test failed).

 

assertTrue (or assertFalse)

assertTrue(expr, msg=None)

Checks that the bool(expr) is True or False.

Note that this method evaluates bool(expr) instead of expr to be True or False.

 

assertIsInstance (or assertNotIsInstance)

assertIsInstance(obj, cls, msg=None)

Determines if the object obj is an instance (or not) of the class cls.

 

assertIs (or assertIsNot)

assertIs(firstObj, secondObj, msg=None)

Tests if the firstObj and secondObj are (or not) the same objects.

 

assertIn (or assertNotIn)

assertIn(member, container, msg=None)

Verifies if the member is (or not) in the container. It is available in version 3.1

 

assertIsNone (or assertIsNotNone)

assertIsNone(expr, msg=None)

Checks whether the expr is None or not.

 

Conclusion

Wrapping it all up, it is good practice to write and run tests in your Django application before deploying it. The approach of testing in Django is straightforward. If you follow all the steps and rules thoroughly then testing can provide you with highly qualitative code with no errors. Furthermore, if you want to dive deeper into the core of Django modules and libraries, its official documentations are super easy to grasp. Have fun coding!