Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock a queryset for use in a for loop in python/Mock

I am writing some unit tests and want to use Mock.

Given the following code:

# the 'real' query set is a Django database model
# qs = SomeDjangoModel.objects.filter(name='some_name')
qs = mock.Mock()
qs.filter.return_value = qs
item = mock.Mock()
item.do_work.return_value = "Some text"
qs.iter.return_value = iter([item])
# below is the code I want to test..
qs = qs.filter(name='some name')
qs = qs.filter(valid_from__lte=Timezone.now())
for obj in qs:
    obj.do_work()

when run, I get

TypeError: 'Mock' object is not iterable

I have tried patching

@mock.patch('__builtin__.iter')

but I just can't seem to get it to work. I haven't succeeded in figuring out what really goes on when the query set "used" by the for-loop.

Help is greatly appreciated!

[edited with further added example code, after first solution proposal]

like image 839
user2879726 Avatar asked Feb 12 '14 15:02

user2879726


People also ask

How do you mock a method inside a class Python?

To mock a method in a class with @patch. object but return a different value each time it is called, use side_effect. Side effect allows you to define a custom method and have that method called each time your mock method is called. The value returned from this method will be used as the return value your mock method.

What is the difference between mock and MagicMock?

So what is the difference between them? MagicMock is a subclass of Mock . It contains all magic methods pre-created and ready to use (e.g. __str__ , __len__ , etc.). Therefore, you should use MagicMock when you need magic methods, and Mock if you don't need them.

What is QuerySet in Python?

A QuerySet is a collection of data from a database. A QuerySet is built up as a list of objects. QuerySets makes it easier to get the data you actually need, by allowing you to filter and order the data. In this tutorial we will be querying data from the Members table.


3 Answers

You have to use iterator and MagicMock which has __iter__ defined

from unittest.mock import Mock, MagicMock
from datetime import datetime

qs = MagicMock()
qs.filter.return_value = qs
item = Mock()
item.do_work.return_value = "Some text"
qs.iterator.return_value = iter([item])
# below is the code I want to test..
qs = qs.filter(name='some name')
qs = qs.filter(valid_from__lte=datetime.now())
for obj in qs:
    obj.do_work()
like image 123
wilcus Avatar answered Sep 29 '22 04:09

wilcus


Usually I mock the QuerySet to be a list instead, that seems easier. So:

something.return_value = [item]

where something is the function or place where you're computing the QuerySet. As an actual example:

MyModel.objects.filter.return_value = [item]

This only works if you're not using QuerySet specific characteristics.

like image 36
Simeon Visser Avatar answered Sep 29 '22 04:09

Simeon Visser


A colleague of mine helped me solve this. The following code does I wanted.

  def the_iter(self):
        return iter(self.my_test_list)

    def test_my_code(self):
        qs = mock.Mock()
        qs.filter.return_value = qs
        the_item = mock.Mock()
        the_item.do_work.return_value = "Some text"
        self.my_test_list = [the_item]
        qs.__iter__ = mock.Mock(side_effect=self.the_iter)

        # below is the code I want to test..
        qs = qs.filter(name='some name')
        qs = qs.filter(colour='Yellow')
        for obj in qs:
            print obj.do_work()
like image 44
user2879726 Avatar answered Sep 29 '22 02:09

user2879726