diff --git a/requests_mock/adapter.py b/requests_mock/adapter.py index e0560b2..b63ac47 100644 --- a/requests_mock/adapter.py +++ b/requests_mock/adapter.py @@ -97,6 +97,7 @@ def __init__(self, method, url, responses, complete_qs, request_headers, self._request_headers = request_headers self._real_http = real_http self._additional_matcher = additional_matcher + self._exhausted = False # url can be a regex object or ANY so don't always run urlparse if isinstance(url, six.string_types): @@ -223,10 +224,19 @@ def __call__(self, request): response_matcher = self._responses.pop(0) else: response_matcher = self._responses[0] + self._exhausted = True self._add_to_history(request) return response_matcher.get_response(request) + @property + def exhausted(self): + return self._exhausted + + def reset(self): + super(_Matcher, self).reset() + self._exhausted = False + class Adapter(BaseAdapter, _RequestHistoryTracker): """A fake adapter than can return predefined responses. @@ -314,6 +324,11 @@ def add_matcher(self, matcher): """ self._matchers.append(matcher) + @property + def all_exhausted(self): + """Return if all matchers have been exhausted.""" + return all(m.exhausted for m in self._matchers) + def reset(self): super(Adapter, self).reset() for matcher in self._matchers: diff --git a/requests_mock/adapter.pyi b/requests_mock/adapter.pyi index dbeba49..08df127 100644 --- a/requests_mock/adapter.pyi +++ b/requests_mock/adapter.pyi @@ -47,7 +47,10 @@ class _Matcher(_RequestHistoryTracker): case_sensitive: Any ) -> None: ... def __call__(self, request: Request) -> Optional[Response]: ... - + @property + def exhausted(self) -> bool: ... + def reset(self) -> None: ... + class Adapter(BaseAdapter, _RequestHistoryTracker): def __init__(self, case_sensitive: bool = ...) -> None: ... def register_uri( @@ -72,4 +75,6 @@ class Adapter(BaseAdapter, _RequestHistoryTracker): **kwargs: Any ) -> _Matcher: ... def add_matcher(self, matcher: Matcher) -> None: ... + @property + def all_exhausted(self) -> bool: ... def reset(self) -> None: ... diff --git a/requests_mock/mocker.py b/requests_mock/mocker.py index d3bc855..4c52ba8 100644 --- a/requests_mock/mocker.py +++ b/requests_mock/mocker.py @@ -132,6 +132,7 @@ def __init__(self, session=None, **kwargs): self._json_encoder = kwargs.pop('json_encoder', None) self.real_http = kwargs.pop('real_http', False) + self.strict = kwargs.pop('strict', False) self._last_send = None if kwargs: @@ -213,6 +214,8 @@ def stop(self): if self._last_send: self._mock_target.send = self._last_send self._last_send = None + if self.strict: + assert self._adapter.all_exhausted # for familiarity with MagicMock def reset_mock(self): diff --git a/tests/test_adapter.py b/tests/test_adapter.py index 2913727..d18abe1 100644 --- a/tests/test_adapter.py +++ b/tests/test_adapter.py @@ -405,6 +405,16 @@ def test_reset_reverts_call_count(self): for matcher in self.adapter._matchers: self.assertEqual(matcher.call_count, 0) + def test_all_exhausted(self): + self.adapter.register_uri('GET', self.url, text='resp') + self.assertFalse(self.adapter.all_exhausted) + + self.session.get(self.url) + self.assertTrue(self.adapter.all_exhausted) + + self.adapter.reset() + self.assertFalse(self.adapter.all_exhausted) + def test_adapter_picks_correct_adapter(self): good = '%s://test3.url/' % self.PREFIX self.adapter.register_uri('GET', diff --git a/tests/test_matcher.py b/tests/test_matcher.py index a30e195..11cb11e 100644 --- a/tests/test_matcher.py +++ b/tests/test_matcher.py @@ -303,7 +303,26 @@ def test_match_body(request): request_data='goodbye world', additional_matcher=test_match_body) - def test_reset_reverts_count(self): + def test_exhausted(self): + url = 'mock://test/site/' + matcher = adapter._Matcher('GET', + url, + [_MatcherResponse(), _MatcherResponse()], + complete_qs=False, + additional_matcher=None, + request_headers={}, + real_http=False, + case_sensitive=False) + request = adapter._RequestObjectProxy._create('GET', url) + + matcher(request) + self.assertEqual(matcher.exhausted, False) + matcher(request) + self.assertEqual(matcher.exhausted, True) + matcher(request) + self.assertEqual(matcher.exhausted, True) + + def test_reset(self): url = 'mock://test/site/' matcher = adapter._Matcher('GET', url, @@ -320,5 +339,7 @@ def test_reset_reverts_count(self): matcher(request) self.assertEqual(matcher.call_count, call_count) + self.assertEqual(matcher.exhausted, True) matcher.reset() self.assertEqual(matcher.call_count, 0) + self.assertEqual(matcher.exhausted, False) diff --git a/tests/test_mocker.py b/tests/test_mocker.py index 7a4061c..2306244 100644 --- a/tests/test_mocker.py +++ b/tests/test_mocker.py @@ -18,6 +18,7 @@ except ImportError: import mock import requests +import testtools import requests_mock from requests_mock import adapter @@ -122,6 +123,20 @@ def test_with_context_manager(self): self._do_test(m) self.assertMockStopped() + def test_strict_with_context_manager(self): + self.assertMockStopped() + with requests_mock.Mocker(strict=True) as m: + self._do_test(m) + self.assertMockStopped() + + def test_strict_with_context_manager_raises(self): + self.assertMockStopped() + with testtools.ExpectedException(AssertionError): + with requests_mock.Mocker(strict=True) as m: + m.register_uri('GET', 'http://www.example.com', text='resp') + self._do_test(m) + self.assertMockStopped() + @mock.patch('requests.adapters.HTTPAdapter.send') @requests_mock.mock(real_http=True) def test_real_http(self, real_send, mocker):