This might be straightforward, but mocking as a whole really confuses me so I'm not sure what to do.
I currently have an external API that I'm working with generated by the Swagger Codegen library. I do not have the original template used to generate it, but I have a library similar to swagger's petshop example. I'm trying to test my code that interacts with the API, but I'm running into trouble testing what happens when an exception is raised by the external API.
Currently I have something like this for my tests:
mock_api = mocker.patch('<swagger_api>.ApiClient.call_api')
mock_api.side_effect = [ApiException(status=429), mocker.DEFAULT]
And my traceback is:
/home/rob/.pyenv/versions/3.6.6/lib/python3.6/unittest/mock.py:939: in __call__
return _mock_self._mock_call(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_mock_self = <MagicMock name='call_api' id='140060372397864'>
args = ('/companies', 'GET', {}, [('next_page', <MagicMock name='call_api().next_page' id='140060365572248'>)], {'Accept': 'application/json'})
kwargs = {'_async': None, '_preload_content': True, '_request_timeout': None, '_return_http_data_only': True, ...}
self = <MagicMock name='call_api' id='140060372397864'>, _new_name = '', _new_parent = None
_call = call('/companies', 'GET', {}, [('next_page', <MagicMock name='call_api().next_page' id='140060365572248'>)], {'Accept'...tings=['ApiKeyAuth'], body=None, collection_formats={}, files={}, post_params=[], response_type='ApiResponseCompanies')
seen = set(), skip_next_dot = False, do_method_calls = False, name = 'call_api'
def _mock_call(_mock_self, *args, **kwargs):
self = _mock_self
self.called = True
self.call_count += 1
_new_name = self._mock_new_name
_new_parent = self._mock_new_parent
_call = _Call((args, kwargs), two=True)
self.call_args = _call
self.call_args_list.append(_call)
self.mock_calls.append(_Call(('', args, kwargs)))
seen = set()
skip_next_dot = _new_name == '()'
do_method_calls = self._mock_parent is not None
name = self._mock_name
while _new_parent is not None:
this_mock_call = _Call((_new_name, args, kwargs))
if _new_parent._mock_new_name:
dot = '.'
if skip_next_dot:
dot = ''
skip_next_dot = False
if _new_parent._mock_new_name == '()':
skip_next_dot = True
_new_name = _new_parent._mock_new_name + dot + _new_name
if do_method_calls:
if _new_name == name:
this_method_call = this_mock_call
else:
this_method_call = _Call((name, args, kwargs))
_new_parent.method_calls.append(this_method_call)
do_method_calls = _new_parent._mock_parent is not None
if do_method_calls:
name = _new_parent._mock_name + '.' + name
_new_parent.mock_calls.append(this_mock_call)
_new_parent = _new_parent._mock_new_parent
# use ids here so as not to call __hash__ on the mocks
_new_parent_id = id(_new_parent)
if _new_parent_id in seen:
break
seen.add(_new_parent_id)
ret_val = DEFAULT
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
raise effect
if not _callable(effect):
> result = next(effect)
E StopIteration
/home/rob/.pyenv/versions/3.6.6/lib/python3.6/unittest/mock.py:998: StopIteration
I don't think this is specific to the API I'm using, so I omitted those parts of the traceback and code for privacy.
What I'd essentially like to happen is:
- Code calls API
- First time API is called, but it raises an exception. I'd prefer not to have to mock out the entire response of the API call.
- Code for handling the exception and retrying calls API again
- This time API calls external API and returns response.
Note that I'm doing this for multiple API calls, so my tests are parameterized with callables, so it's not really possible to mock out the code at an earlier point of the Swagger API cycle. Removing the parameterized tests is one possibility, but I'd prefer another option.
If there's already a library out there that does this, that would be ideal.