Allow custom matchers
Allow users to specify there own matchers. A matcher should take a request and return a response or None.
This commit is contained in:
parent
ce5b885e45
commit
24e7d43a16
@ -167,3 +167,39 @@ Only the headers that are provided need match, any additional headers will be ig
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
requests_mock.exceptions.NoMockAddress: No mock address: POST mock://test.com/headers
|
||||
|
||||
|
||||
Custom Matching
|
||||
===============
|
||||
|
||||
Internally calling :py:meth:`~requests_mock.Adapter.register_uri` creates a *matcher* object for you and adds it to the list of matchers to check against.
|
||||
|
||||
A *matcher* is any callable that takes a :py:class:`requests.Request` and returns a :py:class:`requests.Response` on a successful match or *None* if it does not handle the request.
|
||||
|
||||
If you need more flexibility than provided by :py:meth:`~requests_mock.Adapter.register_uri` then you can add your own *matcher* to the :py:class:`~requests_mock.Adapter`. Custom *matchers* can be used in conjunction with the inbuilt *matchers*. If a matcher returns *None* then the request will be passed to the next *matcher* as with using :py:meth:`~requests_mock.Adapter.register_uri`.
|
||||
|
||||
.. doctest::
|
||||
:hide:
|
||||
|
||||
>>> import requests
|
||||
>>> import requests_mock
|
||||
>>> adapter = requests_mock.Adapter()
|
||||
>>> session = requests.Session()
|
||||
>>> session.mount('mock', adapter)
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> def custom_matcher(request):
|
||||
... if request.path_url == '/test':
|
||||
... resp = requests.Response()
|
||||
... resp.status_code = 200
|
||||
... return resp
|
||||
... return None
|
||||
...
|
||||
>>> adapter.add_matcher(custom_matcher)
|
||||
>>> session.get('mock://test.com/test').status_code
|
||||
200
|
||||
>>> session.get('mock://test.com/other')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
requests_mock.exceptions.NoMockAddress: No mock address: POST mock://test.com/other
|
||||
|
@ -260,11 +260,21 @@ class Adapter(BaseAdapter):
|
||||
response_list = [kwargs]
|
||||
|
||||
responses = [_MatcherResponse(**k) for k in response_list]
|
||||
self._matchers.append(_Matcher(method,
|
||||
url,
|
||||
responses,
|
||||
complete_qs=complete_qs,
|
||||
request_headers=request_headers))
|
||||
self.add_matcher(_Matcher(method,
|
||||
url,
|
||||
responses,
|
||||
complete_qs=complete_qs,
|
||||
request_headers=request_headers))
|
||||
|
||||
def add_matcher(self, matcher):
|
||||
"""Register a custom matcher.
|
||||
|
||||
A matcher is a callable that takes a `requests.Request` and returns a
|
||||
`requests.Response` if it matches or None if not.
|
||||
|
||||
:param callable matcher: The matcher to execute.
|
||||
"""
|
||||
self._matchers.append(matcher)
|
||||
|
||||
@property
|
||||
def last_request(self):
|
||||
|
@ -27,6 +27,7 @@ class MockerCore(object):
|
||||
|
||||
_PROXY_FUNCS = set(['last_request',
|
||||
'register_uri',
|
||||
'add_matcher',
|
||||
'request_history'])
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
82
requests_mock/tests/test_custom_matchers.py
Normal file
82
requests_mock/tests/test_custom_matchers.py
Normal file
@ -0,0 +1,82 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import requests
|
||||
import six
|
||||
|
||||
import requests_mock
|
||||
from requests_mock.tests import base
|
||||
|
||||
|
||||
class FailMatcher(object):
|
||||
|
||||
def __init___(self):
|
||||
self.called = False
|
||||
|
||||
def __call__(self, request):
|
||||
self.called = True
|
||||
return None
|
||||
|
||||
|
||||
def match_all(request):
|
||||
resp = requests.Response()
|
||||
resp.status_code = 200
|
||||
resp._content = six.b('data')
|
||||
resp.request = request
|
||||
resp.encoding = 'utf-8'
|
||||
resp.close = lambda *args, **kwargs: None
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
def test_a(request):
|
||||
if 'a' in request.url:
|
||||
return match_all(request)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class CustomMatchersTests(base.TestCase):
|
||||
|
||||
def assertMatchAll(self, resp):
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual(resp.text, six.u('data'))
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_custom_matcher(self, mocker):
|
||||
mocker.add_matcher(match_all)
|
||||
|
||||
resp = requests.get('http://any/thing')
|
||||
self.assertMatchAll(resp)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_failing_matcher(self, mocker):
|
||||
failer = FailMatcher()
|
||||
|
||||
mocker.add_matcher(match_all)
|
||||
mocker.add_matcher(failer)
|
||||
|
||||
resp = requests.get('http://any/thing')
|
||||
|
||||
self.assertMatchAll(resp)
|
||||
self.assertTrue(failer.called)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_some_pass(self, mocker):
|
||||
mocker.add_matcher(test_a)
|
||||
|
||||
resp = requests.get('http://any/thing')
|
||||
self.assertMatchAll(resp)
|
||||
|
||||
self.assertRaises(requests_mock.NoMockAddress,
|
||||
requests.get,
|
||||
'http://other/thing')
|
Loading…
x
Reference in New Issue
Block a user