Using Mock with Django

Django has lots of great tools for testing. All of them involve creating a test database before the tests are run. This can be slow.

Python Unittests are another important part of testing. They often are very fast. But what if you want to do unit tests that involve Django model instances? This is where mock comes in. Mock lets you create objects that function like model instances without constructing a database. This Celerity Blog post is a great way to get started.

One problem you may encounter while using mock.patch is this error:

AppRegistryNotReady: Apps aren't loaded yet.

This can be solved by adding this to your test:

import django
django.setup()

You might need to setup some paths and specify a settings file. I do not have to do that because I run the tests from within PyCharm and it does some of that stuff automatically.

Here is a sample of how you might test a Django request that accesses a Django model instance:

import unittest
import json

from django.http import JsonResponse

import mock

def get_contract_items_for_ticket(request):
    """
    AJAX function for loading contract item select based on job
    :param request: Django request object
    :return:
    """
    # Import here to avoid circular imports
    from data.models import Job

    job = Job.objects.get(id=int(request.POST['ticket_job_id']))
    ci_choices = get_contract_items(job.number)
    response = {'choices': ci_choices}
    return JsonResponse(response)


class MyTest(unittest.TestCase):
    import django
    django.setup()

    def setUp(self):
        # Setup code here

    def test(self):
        job_w_contract = mock.Mock()
        job_w_contract.number = '2222-22'
        job_w_contract.id = 22

        job_wo_contract = mock.Mock()
        job_wo_contract.number = '1111-11'
        job_wo_contract.id = 1

        # Function for mocking the .get() method
        def get_job(id=None):
            if id == job_w_contract.id:
                return job_w_contract
            else:
                return job_wo_contract

        # Mock the import statement and test the function
        with mock.patch('data.models.Job') as my_model_mock:
            my_model_mock.objects = mock.Mock()  # mocks Job.objects

            # Make Job.objects.get() use get_job()
            conf = {'get.side_effect': get_job}
            my_model_mock.objects.configure_mock(**conf)

            # Make request object for a job with contract items
            mock_request = mock.Mock()
            mock_request.POST = {'ticket_job_id': str(job_w_contract.id)}

            # Do a request that returns choices
            response = get_contract_items_for_ticket(mock_request)
            choices = json.loads(response.content)['choices']
            self.assertTrue(len(choices) > 1)

            # Do a request that does not return choices
            mock_request.POST = {'ticket_job_id': str(job_wo_contract.id)}
            response = get_contract_items_for_ticket(mock_request)
            choices = json.loads(response.content)['choices']
            self.assertTrue(len(choices) == 1)
Advertisements

Python Mock Functions Not Being Called

Are you using mock to mock some functions during testing and mysteriously your tests are ignoring the mock decorator?

One way this can happen is if the function being mocked is either defined in the module that is being tested or is imported into the module being tested. How to get around this?

In my case, after I wasted time figuring out why mock was not working, I ended up modifying the code in my module so that I did not need mock. In the end, I think the modified code is better anyway. Often making code testable, makes it better too.

Mocking Django Timezone

I use django.utils.timezone.now() in a lot of my code. It returns the datetime in the timezone specified in settings.py. One side effect of using this function is that during testing, you may want “now()” to be a fixed date time. No worries. Mock to the rescue. Here is the code:

import datetime

from django.test import TestCase
from django.utils import timezone

import mock

# Make now() a constant
NOW_FOR_TESTING = datetime.datetime(2015, 10, 10, 10)

# This is the function that replaces django.utils.timezone.now()
def mocked_now():
    return NOW_FOR_TESTING

# This function shows that the mocking is in effect even outside of the TestMyTest scope.
def a_func():
    return timezone.now()

@mock.patch('django.utils.timezone.now', side_effect=mocked_now)
class TestMyTest(TestCase):
    def test_time_zone(self, *args):
        # After patching, mock passes in some extra vars. Put *args to handle them.
        self.assertEqual(timezone.now(), NOW_FOR_TESTING)
        self.assertEqual(timezone.now().date(), NOW_FOR_TESTING.date())
        self.assertEqual(mocked_now(), NOW_FOR_TESTING)

Note that you need to add the “*args” argument to the methods in your TestCase class.

Django Testing using Mock

Two important 3rd party testing tools for Django are Mock and Mommy. Both allow you to quickly create instances of Django models for testing. The advantage of Mock is it is fast because it does not require a test database to be setup. It is used primarily for unit testing.

Here is a skeleton of a mock test:

import unittest
import mock

from models import MyModel

class TestMyCode(unittest.TestCase):
    def test_1(self):
        instance = mock.Mock(spec=MyModel)
        # Do something with instance

Put this in your tests.py file for the app. Then run using:

python manage.py test my_app.TestMyCode