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

10,210

Solution 1

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)

Solution 2

@Gin's answer got me most of the way there, but in my case I'm patching MyModel.objects, and the query I'm mocking looks like this:

MyModel.objects.filter(arg1=user, arg2=something_else).order_by('-something').first()

so this worked for me:

@patch('MyModel.objects')
def test_a_function(mock, a_fixture):
    mock.filter.return_value.order_by.return_value.first.return_value = a_fixture
    result = the_func_im_testing(arg1, arg2)
    assert result == 'value'

Also, the order of the patched attributes matters, and must match the order you're calling them within the tested function.

Share:
10,210

Related videos on Youtube

Jens
Author by

Jens

Hi 👋, my name is Jens Lauterbach and I am a backend software engineer with a life long passion for writing code and doing DevOps with Go, AWS and Terraform. I have been "at it" for over 20 years and I do not see me stopping any time soon. Professionally, I am spending my days at Netcentric, mostly building serverless applications. In my spare time I am working on a small tool called ddbt and in general try to hone my skills with different side projects.

Updated on September 15, 2022

Comments

  • Jens
    Jens over 1 year

    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?

  • Stefan Collier
    Stefan Collier about 6 years
    Should that not be 'values')?
  • radtek
    radtek almost 5 years
    This helped, I had to just modify it to match my orm query - mock.filter.return_value.first.return_value
  • DorZ
    DorZ over 4 years
    it returns <MagicMock name='objects.filter().first()' id='2020460232776'> for me and not actually the value.