Merge "Allow arbirtrary matcher to be add to match"
This commit is contained in:
commit
bf7c25f8d4
@ -209,6 +209,38 @@ Only the headers that are provided need match, any additional headers will be ig
|
|||||||
requests_mock.exceptions.NoMockAddress: No mock address: POST mock://test.com/headers
|
requests_mock.exceptions.NoMockAddress: No mock address: POST mock://test.com/headers
|
||||||
|
|
||||||
|
|
||||||
|
Additional Matchers
|
||||||
|
===================
|
||||||
|
|
||||||
|
As distinct from `Custom Matching` below we can add an additional matcher callback that lets us do more dynamic matching in addition to the standard options.
|
||||||
|
This is handled by a callback function that takes the request as a parameter:
|
||||||
|
|
||||||
|
.. doctest::
|
||||||
|
:hide:
|
||||||
|
|
||||||
|
>>> import requests
|
||||||
|
>>> import requests_mock
|
||||||
|
>>> adapter = requests_mock.Adapter()
|
||||||
|
>>> session = requests.Session()
|
||||||
|
>>> session.mount('mock', adapter)
|
||||||
|
|
||||||
|
.. doctest::
|
||||||
|
|
||||||
|
>>> def match_request_text(request):
|
||||||
|
... # request.text may be None, or '' prevents a TypeError.
|
||||||
|
... return 'hello' in (request.text or '')
|
||||||
|
...
|
||||||
|
>>> adapter.register_uri('POST', 'mock://test.com/additional', additional_matcher=match_request_text, text='resp')
|
||||||
|
>>> session.post('mock://test.com/headers', data='hello world').text
|
||||||
|
'resp'
|
||||||
|
>>> resp = session.post('mock://test.com/additional', data='goodbye world')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
requests_mock.exceptions.NoMockAddress: No mock address: POST mock://test.com/additional
|
||||||
|
|
||||||
|
Using this mechanism lets you do custom handling such as parsing yaml or XML structures and matching on features of that data or anything else that is not directly handled via the provided matchers rather than build in every possible option to `requests_mock`.
|
||||||
|
|
||||||
|
|
||||||
Custom Matching
|
Custom Matching
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ class _Matcher(_RequestHistoryTracker):
|
|||||||
"""Contains all the information about a provided URL to match."""
|
"""Contains all the information about a provided URL to match."""
|
||||||
|
|
||||||
def __init__(self, method, url, responses, complete_qs, request_headers,
|
def __init__(self, method, url, responses, complete_qs, request_headers,
|
||||||
real_http, case_sensitive):
|
additional_matcher, real_http, case_sensitive):
|
||||||
"""
|
"""
|
||||||
:param bool complete_qs: Match the entire query string. By default URLs
|
:param bool complete_qs: Match the entire query string. By default URLs
|
||||||
match if all the provided matcher query arguments are matched and
|
match if all the provided matcher query arguments are matched and
|
||||||
@ -81,6 +81,7 @@ class _Matcher(_RequestHistoryTracker):
|
|||||||
self._complete_qs = complete_qs
|
self._complete_qs = complete_qs
|
||||||
self._request_headers = request_headers
|
self._request_headers = request_headers
|
||||||
self._real_http = real_http
|
self._real_http = real_http
|
||||||
|
self._additional_matcher = additional_matcher
|
||||||
|
|
||||||
# url can be a regex object or ANY so don't always run urlparse
|
# url can be a regex object or ANY so don't always run urlparse
|
||||||
if isinstance(url, six.string_types):
|
if isinstance(url, six.string_types):
|
||||||
@ -169,10 +170,20 @@ class _Matcher(_RequestHistoryTracker):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _match_additional(self, request):
|
||||||
|
if callable(self._additional_matcher):
|
||||||
|
return self._additional_matcher(request)
|
||||||
|
|
||||||
|
if self._additional_matcher is not None:
|
||||||
|
raise TypeError("Unexpected format of additional matcher.")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _match(self, request):
|
def _match(self, request):
|
||||||
return (self._match_method(request) and
|
return (self._match_method(request) and
|
||||||
self._match_url(request) and
|
self._match_url(request) and
|
||||||
self._match_headers(request))
|
self._match_headers(request) and
|
||||||
|
self._match_additional(request))
|
||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
if not self._match(request):
|
if not self._match(request):
|
||||||
@ -231,6 +242,7 @@ class Adapter(BaseAdapter, _RequestHistoryTracker):
|
|||||||
:param str url: The URL to match.
|
:param str url: The URL to match.
|
||||||
"""
|
"""
|
||||||
complete_qs = kwargs.pop('complete_qs', False)
|
complete_qs = kwargs.pop('complete_qs', False)
|
||||||
|
additional_matcher = kwargs.pop('additional_matcher', None)
|
||||||
request_headers = kwargs.pop('request_headers', {})
|
request_headers = kwargs.pop('request_headers', {})
|
||||||
real_http = kwargs.pop('_real_http', False)
|
real_http = kwargs.pop('_real_http', False)
|
||||||
|
|
||||||
@ -255,6 +267,7 @@ class Adapter(BaseAdapter, _RequestHistoryTracker):
|
|||||||
responses,
|
responses,
|
||||||
case_sensitive=self._case_sensitive,
|
case_sensitive=self._case_sensitive,
|
||||||
complete_qs=complete_qs,
|
complete_qs=complete_qs,
|
||||||
|
additional_matcher=additional_matcher,
|
||||||
request_headers=request_headers,
|
request_headers=request_headers,
|
||||||
real_http=real_http)
|
real_http=real_http)
|
||||||
self.add_matcher(matcher)
|
self.add_matcher(matcher)
|
||||||
|
@ -27,24 +27,28 @@ class TestMatcher(base.TestCase):
|
|||||||
request_method='GET',
|
request_method='GET',
|
||||||
complete_qs=False,
|
complete_qs=False,
|
||||||
headers=None,
|
headers=None,
|
||||||
|
request_data=None,
|
||||||
request_headers={},
|
request_headers={},
|
||||||
|
additional_matcher=None,
|
||||||
real_http=False,
|
real_http=False,
|
||||||
case_sensitive=False):
|
case_sensitive=False):
|
||||||
matcher = adapter._Matcher(matcher_method,
|
matcher = adapter._Matcher(matcher_method,
|
||||||
target,
|
target,
|
||||||
[],
|
[],
|
||||||
complete_qs,
|
complete_qs=complete_qs,
|
||||||
request_headers,
|
additional_matcher=additional_matcher,
|
||||||
|
request_headers=request_headers,
|
||||||
real_http=real_http,
|
real_http=real_http,
|
||||||
case_sensitive=case_sensitive)
|
case_sensitive=case_sensitive)
|
||||||
request = adapter._RequestObjectProxy._create(request_method,
|
request = adapter._RequestObjectProxy._create(request_method,
|
||||||
url,
|
url,
|
||||||
headers)
|
headers,
|
||||||
|
data=request_data)
|
||||||
return matcher._match(request)
|
return matcher._match(request)
|
||||||
|
|
||||||
def assertMatch(self,
|
def assertMatch(self,
|
||||||
target,
|
target=ANY,
|
||||||
url,
|
url='http://example.com/requests-mock',
|
||||||
matcher_method='GET',
|
matcher_method='GET',
|
||||||
request_method='GET',
|
request_method='GET',
|
||||||
**kwargs):
|
**kwargs):
|
||||||
@ -58,8 +62,8 @@ class TestMatcher(base.TestCase):
|
|||||||
(matcher_method, target, request_method, url))
|
(matcher_method, target, request_method, url))
|
||||||
|
|
||||||
def assertMatchBoth(self,
|
def assertMatchBoth(self,
|
||||||
target,
|
target=ANY,
|
||||||
url,
|
url='http://example.com/requests-mock',
|
||||||
matcher_method='GET',
|
matcher_method='GET',
|
||||||
request_method='GET',
|
request_method='GET',
|
||||||
**kwargs):
|
**kwargs):
|
||||||
@ -75,8 +79,8 @@ class TestMatcher(base.TestCase):
|
|||||||
**kwargs)
|
**kwargs)
|
||||||
|
|
||||||
def assertNoMatch(self,
|
def assertNoMatch(self,
|
||||||
target,
|
target=ANY,
|
||||||
url,
|
url='http://example.com/requests-mock',
|
||||||
matcher_method='GET',
|
matcher_method='GET',
|
||||||
request_method='GET',
|
request_method='GET',
|
||||||
**kwargs):
|
**kwargs):
|
||||||
@ -90,8 +94,8 @@ class TestMatcher(base.TestCase):
|
|||||||
(matcher_method, target, request_method, url))
|
(matcher_method, target, request_method, url))
|
||||||
|
|
||||||
def assertNoMatchBoth(self,
|
def assertNoMatchBoth(self,
|
||||||
target,
|
target=ANY,
|
||||||
url,
|
url='http://example.com/requests-mock',
|
||||||
matcher_method='GET',
|
matcher_method='GET',
|
||||||
request_method='GET',
|
request_method='GET',
|
||||||
**kwargs):
|
**kwargs):
|
||||||
@ -265,3 +269,18 @@ class TestMatcher(base.TestCase):
|
|||||||
|
|
||||||
self.assertSensitiveMatch('http://abc.com/path?abcd=efGH',
|
self.assertSensitiveMatch('http://abc.com/path?abcd=efGH',
|
||||||
'http://abc.com/path?abcd=eFGH')
|
'http://abc.com/path?abcd=eFGH')
|
||||||
|
|
||||||
|
def test_additional_matcher(self):
|
||||||
|
|
||||||
|
def test_match_body(request):
|
||||||
|
return 'hello' in request.text
|
||||||
|
|
||||||
|
self.assertMatch(request_method='POST',
|
||||||
|
matcher_method='POST',
|
||||||
|
request_data='hello world',
|
||||||
|
additional_matcher=test_match_body)
|
||||||
|
|
||||||
|
self.assertNoMatch(request_method='POST',
|
||||||
|
matcher_method='POST',
|
||||||
|
request_data='goodbye world',
|
||||||
|
additional_matcher=test_match_body)
|
||||||
|
@ -409,3 +409,20 @@ class MockerHttpMethodsTests(base.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(6, m1.call_count)
|
self.assertEqual(6, m1.call_count)
|
||||||
self.assertEqual(2, r1.call_count)
|
self.assertEqual(2, r1.call_count)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_mocker_additional(self, m):
|
||||||
|
url = 'http://www.example.com'
|
||||||
|
good_text = 'success'
|
||||||
|
|
||||||
|
def additional_cb(req):
|
||||||
|
return 'hello' in req.text
|
||||||
|
|
||||||
|
m.post(url, additional_matcher=additional_cb, text=good_text)
|
||||||
|
|
||||||
|
self.assertEqual(good_text,
|
||||||
|
requests.post(url, data='hello world').text)
|
||||||
|
self.assertRaises(exceptions.NoMockAddress,
|
||||||
|
requests.post,
|
||||||
|
url,
|
||||||
|
data='goodbye world')
|
||||||
|
Loading…
Reference in New Issue
Block a user