Django ORM - mock values().filter() chain

Jens picture Jens · Oct 14, 2012 · Viewed 7.6k times · Source

I am trying to mock a chained call on the Djangos model.Manager() class. For now I want to mock the values() and filter() method.

To test that I created a little test project:

  1. Create a virtual environment
  2. Run pip install django mock mock-django nose django-nose
  3. Create a project django-admin.py startproject mocktest
  4. Create an app manage.py startapp mockme
  5. Add django_nose and mocktest.mockme to INSTALLED_APPS (settings.py)
  6. Add TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' to settings.py

To verfiy that everything is setup correctly I ran manage.py test. One test is run, the standard test Django creates when you create an app.

Next thing I did was to create a very simple model.

mockme/models.py

from django.db import models

class MyModel(models.Model):
    name = models.CharField(max_length=50)

Next thing I did was to create a simple function that uses MyModel. That's the function I want to test later.

mockme/functions.py

from models import MyModel

def chained_query():
    return MyModel.objects.values('name').filter(name='Frank')

Nothing special is happening here. The function is filtering the MyModel objects to find all instances where name='Frank'. The call to values() will return a ValuesQuerySet which will only contain the name field of all found MyModel instances.

mockme/tests.py

import mock

from django.test import TestCase
from mocktest.mockme.models import MyModel
from mocktest.mockme.functions import chained_query
from mock_django.query import QuerySetMock

class SimpleTest(TestCase):
    def test_chained_query(self):
        # without mocked queryset the result should be 0
        result = chained_query()
        self.assertEquals(result.count(), 0)

        # now try to mock values().filter() and reeturn
        # one 'Frank'.
        qsm = QuerySetMock(MyModel, MyModel(name='Frank'))
        with mock.patch('django.db.models.Manager.filter', qsm):
            result = chained_query()
            self.assertEquals(result.count(), 1)

The first assertEquals will evaluate as successful. No instances are returned since the model Manager is not mocked yet. When the second assertEquals is called I expect result to contain the MyModel instance I added as return value to the QuerySetMock:

qsm = QuerySetMock(MyModel, MyModel(name='Frank'))

I mocked the filter() method and not the values() method since I found it'll be the last evaluated call, though I am not sure.

The test will fail because the second result variable won't contain any MyModel instances.

To be sure that the filter() method is really mocked I added a "debug print" statement:

from django.db import models
print models.Manager.filter

which returned:

<SharedMock name='mock.iterator' id='4514208912'>

What am I doing wrong?

Answer

Gin picture Gin · Jul 25, 2014

Try this:

import mock
from mocktest.mockme.models import MyModel

class SimpleTest(TestCase):
    def test_chained_query(self):
        my_model_value_mock = mock.patch(MyModel.objects, 'value')
        my_model_value_mock.return_value.filter.return_value.count.return_value = 10000
        self.assertTrue(my_model_value_mock.return_value.filter.return_value.count.called)