在Django中使用patch装饰器模拟模块时出错

3 投票
1 回答
1480 浏览
提问于 2025-04-18 14:43

我正在学习Django中的模拟(mocking),但是对补丁(patching)这个概念有点搞不清楚。

在我的测试中,test_get_name这个测试通过了,但test_patched_get_name却失败了,错误信息是ValueError: Object is of type 'MagicMock', but must be a Django Model, Manager, or QuerySet

这是我的文件:

utils.py

def get_name(clientid):
    c = get_object_or_404(Client, pk=clientid)
    c_name = c.get_name()
    return c_name

tests.py

class ClientTests(unittest.TestCase):
    def setUp(self):
        self.c_instance = mock.Mock(spec=Client, pk=1)
        self.c_instance.name = "Test"

    def test_get_name(self):
        self.assertEqual(Client.get_name(self.c_instance), "Test")

    def test_patched_get_name(self):
        with mock.patch('myapp.utils.Client') as self.c_instance:
            from myapp.utils import get_name
            self.assertEqual(get_name(self.c_instance.pk), "Test")

models.py

class Client(models.Model):
    name = models.CharField(max_length=200)

    def get_name(self):
        return self.name

我猜get_object_or_404需要的是一个模型,而不是一个模拟对象,但我不太确定该怎么做。

完整的错误追踪信息:

======================================================================
ERROR: test_patched_get_name (myapp.tests.ClientTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "pathto/myapp/tests.py", line 63, in test_patched_get_name
    self.assertEqual(get_name(self.c_instance.pk)
  File "pathto/myapp/views.py", line 36, in get_name
    cu = get_object_or_404(Client, pk=clientid)
  File "pathto/lib/python2.7/site-packages/django/shortcuts/__init__.py", line 111, in get_object_or_404
    queryset = _get_queryset(klass)
  File "pathto/lib/python2.7/site-packages/django/shortcuts/__init__.py", line 97, in _get_queryset
    "Manager, or QuerySet" % klass__name)
ValueError: Object is of type 'MagicMock', but must be a Django Model, Manager, or QuerySet

----------------------------------------------------------------------

1 个回答

1

如果你想测试 get_name 这个函数,我觉得你应该先对 Django 的 get_object_or_404 进行一些修改。简单来说,就是在测试的时候,当调用 get_name 时,不是去调用 Django 的 get_object_or_404,而是调用一个模拟的对象——也就是你修改过的那个函数——这个模拟函数会返回你预先设定好的值。这样,你的测试就只需要验证 get_name 是否正常工作,并且返回正确的值。实际上并不需要真的去调用 Django 的 get_object_or_404(因为那不是你要测试的内容)。

我觉得你的测试应该像这样:

# make sure you import "patch" from mock
from mock import patch

# Patch the "get_object_or_404" function used inside "get_name"
@patch('myapp.utils.get_object_or_404')
def test_patched_get_name(self, mock_get_object_or_404):

    # The patched function is supplied as a parameter to your test function
    # -- you can give it any name you want (in this case, it's
    # "mock_get_object_or_404").

    from myapp.utils import get_name

    # Set mock function's return value -- the patched function here
    # will return the mock "Client" object.
    mock_get_object_or_404.return_value = self.c_instance

    # Now compare the return value from the "get_name" function to
    # see if it's the same as the dummy client's name.
    returned_value = get_name(self.c_instance.pk)
    expected_value = self.c_instance.name
    self.assertEqual(returned_value, expected_value)

撰写回答