修补函数的assert_call_with

时间:2019-03-19 12:19:05

标签: python mocking

我试图断言一个包装在另一个函数内的补丁request.get()调用的输入。

def get_data(*args):
    # logic to define url, based on '*args'
    url = 'some_url?arg1&arg3'

    # call I want to patch and assert the url of
    response = request.get(url)

    # process response
    stuff = 'processed_response'
    return stuff

测试脚本:

def mock_response_200(url):
    response = mock.MagicMock()
    response.status_code = 200
    response.json = mock.Mock(return_value={  
        0: {'key1': 'value1', 'key2': 'value2'}
    })
    return response

@mock.patch('request.get', new=mock_response_200)
def test_get_data():
    arg1 = 'arg1'
    arg2 = None
    arg3 = 'arg3'

    stuff = get_data(arg1, arg2, arg3)
    # <assert input arguments of patched function here>

我该如何断言传递给mocked_response_200的网址?在test_get_data中“ mocked_response_200”未知。
我在This one comes close处检查了其他帖子,但答案使用了不同的补丁方法。任何帮助将不胜感激。

3 个答案:

答案 0 :(得分:1)

您正在修补get模块中的未知request对象。您可能没有这样的模块或对象。

您需要处理被测模块中的request对象 。如果get_data位于模块views中,则需要在此处修补views.request.get

@mock.patch('views.request.get', new=mock_response_200)

来自mock.patch() documentation

  

target 应该是'package.module.ClassName'形式的字符串。目标已导入,指定对象被新对象替换,因此目标必须可从您从其调用patch()的环境中导入。在执行装饰功能时(而不是在装饰时)导入目标。

但是,我根本不会在这里使用任何功能。只需让模拟为您修补get()方法,然后配置该模拟对象即可。您当然可以将它委托给一个辅助函数:

def config_response_200_mock(request_get):
    response = request_get.return_value
    response.status_code = 200
    response.json.return_value = {  
        0: {'key1': 'value1', 'key2': 'value2'}
    }
    return response

@mock.patch('views.request.get')
def test_get_data(request_get):
    response_mock = config_response_200_mock(request_get)

    arg1 = 'arg1'
    arg2 = None
    arg3 = 'arg3'

    stuff = get_data(arg1, arg2, arg3)

您还可以在此类函数中创建一个魔术模拟对象,然后将该函数传递给new_callable上的mock.patch()

def response_200_mock():
    get_mock = mock.MagicMock()
    response = get_mock.return_value
    response.status_code = 200
    response.json.return_value = {  
        0: {'key1': 'value1', 'key2': 'value2'}
    }
    return get_mock

@mock.patch('views.request.get', new_callable=response_200_mock)
def test_get_data(request_get):
    arg1 = 'arg1'
    arg2 = None
    arg3 = 'arg3'

    stuff = get_data(arg1, arg2, arg3)

无论哪种方式,用于修补request.get的对象都作为参数传递给test_get_data

这两种方法的演示(使用patch作为上下文管理器而不是装饰器,但是原理是相同的:

>>> def config_response_200_mock(request_get):
...     response = request_get.return_value
...     response.status_code = 200
...     response.json.return_value = {
...         0: {'key1': 'value1', 'key2': 'value2'}
...     }
...     return response
...
>>> with mock.patch('__main__.request.get') as request_get:
...     response_mock = config_response_200_mock(request_get)
...     arg1 = 'arg1'
...     arg2 = None
...     arg3 = 'arg3'
...     stuff = get_data(arg1, arg2, arg3)
...
>>> stuff
'processed_response'
>>> response_mock.json()
{0: {'key1': 'value1', 'key2': 'value2'}}
>>> request_get.mock_calls
[call('some_url?arg1&arg3')]
>>> def response_200_mock():
...     get_mock = mock.MagicMock()
...     response = get_mock.return_value
...     response.status_code = 200
...     response.json.return_value = {
...         0: {'key1': 'value1', 'key2': 'value2'}
...     }
...     return get_mock
...
>>> with mock.patch('__main__.request.get', new_callable=response_200_mock) as request_get:
...     arg1 = 'arg1'
...     arg2 = None
...     arg3 = 'arg3'
...     stuff = get_data(arg1, arg2, arg3)
...
>>> stuff
'processed_response'
>>> request_get.return_value.json()
{0: {'key1': 'value1', 'key2': 'value2'}}
>>> request_get.mock_calls
[call('some_url?arg1&arg3')]

答案 1 :(得分:0)

Unittest是一次测试一个基本组件。因此,在内部调用的所有其他组件都需要在其他测试中进行测试。

如果您只想断言url已正确传递,那么我建议不要使用new关键字

@mock.patch('module.process_response')
@mock.patch('module.request.get')
def test_get_data(mock_get, mock_process_response):
    arg1 = 'arg1'
    arg2 = None
    arg3 = 'arg3'
    url = "what ever url"

    stuff = get_data(arg1, arg2, arg3)
    mock_get.assert_called_with(url)

这将帮助您确定调用是否正确。在以后的测试中,使用new关键字检查返回的响应是否正确处理。

mock_process_responseMagicMock对象,将阻止调用process_response,因此将不需要mock_get来定义json或{{1} }。

编辑:为status_code添加了模拟。

答案 2 :(得分:0)

首先,非常感谢@ Ja8zyjits和@Martijn Pieters的帮助。
适用于我的解决方案如下:

@mock.patch('request.get', side_effect=mock_response_200)
def test_get_data(mock_get):
    arg1 = 'arg1'
    arg2 = None
    arg3 = 'arg3'
    expected_url = 'some_url?arg1&arg3'

    stuff = get_data(arg1, arg2, arg3)

    mock_get.assert_called_with(url)

我不能说我完全理解将mock_response_200传递为'side_effect'和将mock_get传递给test_get_data之间的相互作用。但是使用这种组合,我既可以断言修补的request.get的输入,又可以返回所需的响应,以防止在响应处理期间引发任何错误。

编辑:代码格式