Mocking a Django Queryset in order to test a function that takes a queryset

23,230

Solution 1

Not that I know of, but why not use an actual queryset? The test framework is all set up to allow you to create sample data within your test, and the database is re-created on every test, so there doesn't seem to be any reason not to use the real thing.

Solution 2

Of course you can mock a QuerySet, you can mock anything.

You can create an object yourself, and give it the interface you need, and have it return any data you like. At heart, mocking is nothing more than providing a "test double" that acts enough like the real thing for your tests' purposes.

The low-tech way to get started is to define an object:

class MockQuerySet(object):
    pass

then create one of these, and hand it to your test. The test will fail, likely on an AttributeError. That will tell you what you need to implement on your MockQuerySet. Repeat until your object is rich enough for your tests.

Solution 3

For an empty Queryset, I'd go simply for using none as keithhackbarth has already stated.

However, to mock a Queryset that will return a list of values, I prefer to use a Mock with a spec of the Model's manager. As an example (Python 2.7 style - I've used the external Mock library), here's a simple test where the Queryset is filtered and then counted:

from django.test import TestCase
from mock import Mock

from .models import Example


def queryset_func(queryset, filter_value):
    """
    An example function to be tested
    """
    return queryset.filter(stuff=filter_value).count()


class TestQuerysetFunc(TestCase):

    def test_happy(self):
        """
        `queryset_func` filters provided queryset and counts result
        """
        m_queryset = Mock(spec=Example.objects)
        m_queryset.filter.return_value = m_queryset
        m_queryset.count.return_value = 97

        result = func_to_test(m_queryset, '__TEST_VALUE__')

        self.assertEqual(result, 97)
        m_queryset.filter.assert_called_once_with(stuff='__TEST_VALUE__')
        m_queryset.count.assert_called_once_with()

However, to fulfil the question, instead of setting a return_value for count, this could easily be adjusted to be a list of model instances returned from all.

Note that chaining is handled by setting the filter to return the mocked queryset:

m_queryset.filter.return_value = m_queryset

This would need to be applied for any queryset methods used in the function under test, e.g. exclude, etc.

Solution 4

I am having the same issue, and it looks like some nice person has written a library for mocking QuerySets, it is called mock-django and the specific code you will need is here https://github.com/dcramer/mock-django/blob/master/mock_django/query.py I think you can then just patch you models objects function to return one of these QuerySetMock objects that you have set up to return something expected!

Solution 5

For this I use Django's .none() function.

For example:

class Location(models.Model):
  name = models.CharField(max_length=100)
mock_locations = Location.objects.none()

This is the method used frequently in Django's own internal test cases. Based on comments in the code

Calling none() will create a queryset that never returns any objects and no
+query will be executed when accessing the results. A qs.none() queryset
+is an instance of ``EmptyQuerySet``.
Share:
23,230
Amandasaurus
Author by

Amandasaurus

I'm a Linux user

Updated on April 10, 2020

Comments

  • Amandasaurus
    Amandasaurus about 4 years

    I have a utility function in my Django project, it takes a queryset, gets some data from it and returns a result. I'd like to write some tests for this function. Is there anyway to 'mock' a QuerySet? I'd like to create an object that doesn't touch the database, and i can provide it with a list of values to use (i.e. some fake rows) and then it'll act just like a queryset, and will allow someone to do field lookups on it/filter/get/all etc.

    Does anything like this exist already?

  • yantrab
    yantrab over 12 years
    The reason to mock QuerySet is to make the tests faster, and also reduce the complexity of running them.
  • Amandasaurus
    Amandasaurus over 12 years
    Yes, I know it's relatively simple to mock it. But I'm wondering if someone else has created all the filter/exclude methods, before I go and implement all of them. :)
  • mkelley33
    mkelley33 about 12 years
    Some other advantages to what @NedBatchelder has said is that your tests will most likely be run more often by more developers (you or your team) and fail faster, which lets you catch bugs and changes more frequently. I tend to mock methods on model managers when testing views, but I'd like to know more about mocking QuerySet and other strategies for improving my tests.
  • Danny Staple
    Danny Staple about 12 years
    I agree with Ned on the speed front, and also this becomes far less reliable if using the Django ORM standalone (perhaps an unusual use case) - the 'automatic' test database is not created for you, and you NEVER should be running test cases on the live database.
  • Colin Su
    Colin Su over 9 years
    actual QuerySet is not guarantee to be right, use as simple as possible mocking queryset to avoid the condition that both of them are in the wrong status in the same time.
  • jamesc
    jamesc about 8 years
    TIL .none! Thanks - this is super helpful.
  • wim
    wim over 7 years
    Works a treat, thanks. This should be the accepted answer!