使用patch模拟Python中的Celery任务调用

8 投票
1 回答
4499 浏览
提问于 2025-04-18 00:07

在使用模拟(mock)来修补一个Celery任务调用时,我得到的结果是<Mock name='mock().get()' ...>,而不是我期望的通过mock_task.get.return_value = "value"定义的return_value。不过,在我的单元测试中,这个模拟的任务运行得很好。

这是我修补Celery任务的单元测试:

def test_foo(self):

    mock_task = Mock()
    mock_task.get = Mock(return_value={'success': True})

    print mock_task.get() # outputs {'success': True}

    with patch('app.tasks.my_task.delay', new=mock_task) as mocked_task:
        foo()  # this calls the mocked task with an argument, 'input from foo'
        mock_tasked.assert_called_with('input from foo')  # works

这是正在测试的函数:

def foo():
    print tasks.my_task.delay  # shows a Mock object, as expected
    # now let's call get() on the mocked task:
    task_result = tasks.my_task.delay('input from foo').get()
    print task_result  # => <Mock name='mock().get()' id='122741648'>
    # unexpectedly, this does not return {'success': True}
    if task_result['success']:
        ...

最后一行抛出了一个错误:TypeError: 'Mock' object has no attribute '__getitem__'

为什么我可以在单元测试中调用mock_task.get(),但是在foo中调用它却返回了一个<Mock ...>而不是我期望的返回值呢?

1 个回答

8

很遗憾,我对Celery几乎一无所知,但看起来问题出在模拟(mock)上。

你有:

tasks.my_task.delay('input from foo').get()

在执行patch('app.tasks.my_task.delay', new=mock_task)之后,它变成了:

mock_task('input from foo').get()

这和下面的内容是不一样的:

mock_task.get()

你应该把模拟的创建改成:

mock_task().get = Mock(return_value={'success': True})

当你访问已有的模拟属性或调用它时,会默认创建一个新的模拟实例。所以我们可以稍微简化一下:

mock_task().get.return_value = {'success': True}

撰写回答