7

I would like to mock the following CanonPerson model

def compute(self, is_send_emails, test_email_address):
        cpses = CanonPerson.objects.filter(persons__vpd=6,
                                           persons__country="United States",
                                           persons__role__icontains=';IX;').prefetch_related("persons").using("global")

        for cp in cpses:
           ...

I am quite lost how to mock CanonPerson.objects.filter to return me an interable collection, so that I can continue.

This is what I have done so far:

def test_X_count(self):
    with mock.patch('apps.dbank.models.CanonPerson.objects.filter') as canon_patch:
        mock_cp = mock.MagicMock(spec=CanonPerson)
        mock_person = mock.MagicMock(spec=Person)
        mock_person.vpd = 6
        mock_cp.country = "United States"
        mock_cp.role = ";IX;"
        mock_cp.persons.add(mock_person)
        canon_patch.objects.filter.return_value = [mock_cp] // ???
        oi = OptinInvites()
        oi.compute(False, None)
        oi.get_most_recent_email.assert_called_once_with(1)

In the compute function I can see cpses is a MagicMock type. However it is not iterable and the for loop there after, just jumps over it. I thought by setting the return value to [mock_cp] I would have created an iterable list?

Houman
  • 64,245
  • 87
  • 278
  • 460
  • Try to use [mock_django](https://github.com/dcramer/mock-django). Maybe it will helpfull for you. – Alex Lisovoy Oct 24 '14 at 11:45
  • Why not create some actual data within the test? That's the normal way to do things. Or use something like [factory boy](http://factoryboy.readthedocs.org/en/latest/). – Daniel Roseman Oct 24 '14 at 11:49
  • One reason for wanting to just mock out the django model is if you want to write very fast unit tests. Not mocking out the model is great if you want to write fuller integrationy tests. Both approaches have their place. – aychedee Oct 24 '14 at 12:13

1 Answers1

9

The line where you assign a return value to canon_patch is slightly wrong. Where you have:

canon_patch.objects.filter.return_value = [mock_cp]

It should be:

canon_patch.return_value = [mock_cp]

canon_patch is already the mock of 'objects.filter'.

Your original line would return [mock_cp] if you called CanonPerson.objects.filter.objects.filter().

If you actually want to patch out the CanonPerson model then your patch line would look something like:

with mock.patch('apps.dbank.models.CanonPerson') as canon_patch:

You might also find that you need to mock it in the location that it is being used, rather than the location you are importing it from. So assuming you are using CanonPerson in a module called my_module your patch code in the test might look like this:

with mock.patch('my_module.CanonPerson') as canon_patch:
aychedee
  • 24,871
  • 8
  • 79
  • 83
  • What you are saying makes perfectly sense. I have changed it to with mock.patch('apps.dbank.models.CanonPerson') as canon_patch: however cpses = CanonPerson.objects.filter( is pointing to the real model instead of the mock object. Why has the patch failed to mock the model application wide though? – Houman Oct 24 '14 at 14:30
  • Try mocking it in the module you are using it in. Rather than the module you are importing it from. Does that make sense? `your.module.CanonUser`. – aychedee Oct 24 '14 at 16:09
  • I am not sure, do you mean I have to create the `canon_patch` mock and pass it into `oi.compute(False, None)`? But that would mean I had to change the real code to accommodate for mock objects to be injected in. I was hoping not having to do this. – Houman Oct 27 '14 at 09:27
  • I've added a bit to the end of the answer, hopefully that should be a bit clearer. You don't need to modify your actual code. Just the test code. – aychedee Oct 27 '14 at 10:23