简体   繁体   中英

assert_called_with of patched function

I'm trying to assert the input of a patched request.get() call which is wrapped inside another function.

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

test script:

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>

How do I assert the url which was passed to mocked_response_200? mocked_response_200 isn't 'known' within test_get_data.
I've checked other posts on here This one comes close but the answer uses a different patch method. Any help would be greatly appreciated.

You are patching an unknown get object in the request module. You probably don't have such a module or object.

You need to address the request object in the module under test . If get_data lives in the module views , then you need to patch views.request.get here:

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

From the mock.patch() documentation :

target should be a string in the form 'package.module.ClassName' . The target is imported and the specified object replaced with the new object, so the target must be importable from the environment you are calling patch() from. The target is imported when the decorated function is executed, not at decoration time.

I'd not use a function here at all however. Just let mock patch the get() method for you, then configure that mock object. You can of course delegate that to a helper function:

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)

You can also create a magic mock object in such a function, then pass in the function to new_callable on 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)

Either way, the object used to patch request.get with is passed into test_get_data as an argument.

Demo of either approach (using patch as a context manager instead of a decorator, but the principle is the same:

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

Unittest is to test one basic component at a time. So every other component called within needs to be tested in other tests.

If you only want to assert url was passed properly then I would suggest not to use new keyword

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

This will help you to identify if it was called properly. In the later tests use the new keyword to check if the response returned was processed_properly.

The mock_process_response is a MagicMock object and will prevent process_response from being called and hence wont require the mock_get to define json or status_code to be defined.

edits: added mock for process_response .

First of all, many thanks to @Ja8zyjits and @Martijn Pieters for their help.
The solution which worked for me looks as follows:

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

I can't say I fully understand the interaction between passing mock_response_200 as 'side_effect' and passing mock_get to test_get_data yet. But using this combination I was able to both assert the input of the patched request.get and return the desired response to prevent raising any errors during processing of the response.

edit: code formatting

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM