Django Testing

One of the most important aspect for frameworks are testing your code.
Django provides in-built support for running tests on api's, views, models and all the other utilty functions you might write.

Tests broadly can be of two types:

  1. Unit Tests
  2. Functional Tests/Integration Test

Unit Tests

As the name suggests is testing an individual unit, such as a method (function) in a class, with all dependencies mocked up.
Suppose we have a function like this.

def foo(s):
    if s == ‘bar’:
        return True
    return False

Unit test for this will look like.

class TestCase(SimpleTestCase):

    def test_foo_is_true(self):
        self.assertTrue(foo(‘bar’))
        self.assertFalse(foo(‘something_else’))

Simple right.

So if you put this test on your test.py and run python manage.py test it will and immediatly give back the result.

Functional Tests

Functional tests is testing a slice of functionality in a system. This will test many methods and may interact with dependencies like Databases or Web Services.
For example testing an apiview.

class UserRegistrationAPIView(CreateAPIView):
    authentication_classes = ()
    permission_classes = ()
    serializer_class = UserRegistrationSerializer

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)

        user = serializer.instance
        token, created = Token.objects.get_or_create(user=user)
        data = serializer.data
        data["token"] = token.key

        headers = self.get_success_headers(serializer.data)
        return Response(data, status=status.HTTP_201_CREATED, headers=headers)

Test might look like.

class UserRegistrationAPIViewTestCase(APITestCase):
    url = reverse("users:list")

    def test_invalid_password(self):
        """
        Test to verify that a post call with invalid passwords
        """
        user_data = {
            "username": "testuser",
            "email": "test@testuser.com",
            "password": "password",
            "confirm_password": "INVALID_PASSWORD"
        }
        response = self.client.post(self.url, user_data)
        self.assertEqual(400, response.status_code)

    def test_user_registration(self):
        """
        Test to verify that a post call with user valid data
        """
        user_data = {
            "username": "testuser",
            "email": "test@testuser.com",
            "password": "123123",
            "confirm_password": "123123"
        }
        response = self.client.post(self.url, user_data)
        self.assertEqual(201, response.status_code)
        self.assertTrue("token" in json.loads(response.content))

    def test_unique_username_validation(self):
        """
        Test to verify that a post call with already exists username
        """
        user_data_1 = {
            "username": "testuser",
            "email": "test@testuser.com",
            "password": "123123",
            "confirm_password": "123123"
        }
        response = self.client.post(self.url, user_data_1)
        self.assertEqual(201, response.status_code)

        user_data_2 = {
            "username": "testuser",
            "email": "test2@testuser.com",
            "password": "123123",
            "confirm_password": "123123"
        }
        response = self.client.post(self.url, user_data_2)
        self.assertEqual(400, response.status_code)

Django and more importantly django-rest-framework provides in-built functionality that creates test-db before tests are run and deletes it as soon as testing ends. This reduces a lot of work for mocking the database.

You might want to use django-nose and coverage to check how much test coverage your code has.

Checkout this sample to understand testing protocols in django.

For further reading this and this should be useful.