Django ORM - mock values().filter() chain
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.
Related videos on Youtube
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, 2022Comments
-
Jens over 1 year
I am trying to mock a chained call on the Djangos
model.Manager()
class. For now I want to mock thevalues()
andfilter()
method.To test that I created a little test project:
- Create a virtual environment
- Run
pip install django mock mock-django nose django-nose
- Create a project
django-admin.py startproject mocktest
- Create an app
manage.py startapp mockme
- Add
django_nose
andmocktest.mockme
toINSTALLED_APPS
(settings.py) - 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 aValuesQuerySet
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 thevalues()
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 about 6 yearsShould that not be
'values')
? -
radtek almost 5 yearsThis helped, I had to just modify it to match my orm query -
mock.filter.return_value.first.return_value
-
DorZ over 4 yearsit returns <MagicMock name='objects.filter().first()' id='2020460232776'> for me and not actually the value.