来自相关对象的 Django 单元测试模拟查询集

Django unit test mock queryset from related object

我有以下功能:

import unittest
from unittest import mock


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

我想模拟 filter 方法和 order_by 来检查调用的参数。

我试过了:

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')

我试过的另一个模拟:

@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')

在最后两个模拟中我有一个错误 path.Order 不存在。 我已经对 Payment.objects.filter() 这样的查询使用了直接模拟并且正在工作,但是从 Order 这样的相关模型开始我失败了。

OrderPayment 之间的关系如您所料,一对多。

通过模拟对象我解决了这个问题。

    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')

解释一下这里发生了什么:QuerySet 方法,如 filter()exclude()order_by() 等。return 一个 QuerySet,这就是它们可以链接的原因。

您首先尝试的是尝试修补方法,就好像它们在包层次结构中一样。您最终做的不是修补方法,而是模拟每个链接方法的 return 值,这就是它的完成方式。

不幸的是,关于这方面的文档不多。当我 运行 解决这个问题时,我得到了一些对我有帮助的 Whosebug 答案,但我似乎再也找不到它们了。

类似问题(答案并没有真正提供解释):

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

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

有些库可以帮助您: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,因为它可以帮助您创建简单的模型模拟。