Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django unit test mock queryset from related object

I have the following function:

import unittest
from unittest import mock


def get_payments(order):
    return order.payments.filter(status='complete').order_by('-date_added)

I want to mock the filter method and the order_by to check the arguments with which are called.

I tried:

class TestPayments(unittest.TestCase):
     @mock.patch('path.Order.payments.filter.order_by')
     @mock.patch('path.Order.payments.filter')
     def test_get_payments(self, mock1, mock2):
        mock1.assert_called_with(status='complete')
        mock2.assert_called_with('-date_added')

Another mock I tried:

@mock.patch('path.Payment.objects.filter.order_by')
@mock.patch('path.Payment.objects.filter')

@mock.patch('path.Order.payments.objects.filter.order_by')
@mock.patch('path.Order.payments.objects.filter')

In last two mocks I have an error that path.Order does not exists. I already used a direct mock for a query like Payment.objects.filter() and is working, but starting from a related model like Order I failed.

The relationship between Order and Payment is how you would expect, one to many.

like image 605
jalanga Avatar asked Nov 03 '17 14:11

jalanga


2 Answers

by mocking objects I resolved this.

    order = MagicMock(side_effect=Order())
    order.payments.filter.return_value = MagicMock(side_effect=Payment.objects.filter(id=0))
    order.payments.filter.return_value.order_by.return_value = [Payment()]

    order.payments.filter.assert_called_with(status='complete')
    order.payments.filter.return_value.order_by.assert_called_with('-date_updated')
like image 102
jalanga Avatar answered Nov 17 '22 03:11

jalanga


To explain what happens here: QuerySet methods like filter(), exclude(), order_by() etc. return a QuerySet, that is why they can be chained.

What you tried first is trying to patch the methods as if they would be in a package hierarchy. What you ended up doing is not patching the method but mocking the return value for each chained method, which is how it is done.

Unfortunately there is not much documentation on this. When I ran into this problem I had some stackoverflow answers that helped me, but I can't seem to find them again.

Similar questions (where the answers don't really provide an explaination):

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

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

There are libraries out there that might help you: mock-django provides an ORM mocking class. Their approach to mocking QuerySet methods is quite interesting. What I personally found very useful for testing Django models is Model Mommy, because it helps you to create easy mocks of models.

like image 2
masterfloda Avatar answered Nov 17 '22 01:11

masterfloda