1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
|
================
Request Matching
================
Whilst it is preferable to provide the whole URI to :py:meth:`requests_mock.Adapter.register_uri` it is possible to just specify components.
The examples in this file are loaded with:
.. doctest::
>>> import requests
>>> import requests_mock
>>> adapter = requests_mock.Adapter()
>>> session = requests.Session()
>>> session.mount('mock://', adapter)
.. note::
The examples within use this syntax because request matching is a function of the adapter and not the mocker.
All the same arguments can be provided to the mocker if that is how you use `requests_mock` within your project, and use the
.. code:: python
mock.get(url, ...)
form in place of the given:
.. code:: python
adapter.register_uri('GET', url, ...)
If you are not familiar with `requests <https://requests.readthedocs.io/>`_' adapters (see :ref:`Adapter`),
prefer the mocker approach (see :ref:`Mocker`).
.. doctest::
:hide:
>>> import requests
>>> import requests_mock
>>> adapter = requests_mock.Adapter()
>>> session = requests.Session()
>>> session.mount('mock://', adapter)
.. note::
By default all matching is case insensitive. This can be adjusted by
passing case_sensitive=True when creating a mocker or adapter, or globally
by doing:
.. code:: python
requests_mock.mock.case_sensitive = True
for more, see: :ref:`case_insensitive`
Simple
======
The most simple way to match a request is to register the URL and method that will be requested with a textual response.
When a request is made that goes through the mocker this response will be retrieved.
.. doctest::
.. >>> adapter.register_uri('GET', 'mock://test.com/path', text='resp')
.. >>> session.get('mock://test.com/path').text
.. 'resp'
Path Matching
=============
You can specify a protocol-less path:
.. doctest::
.. >>> adapter.register_uri('GET', '//test.com/', text='resp')
.. >>> session.get('mock://test.com/').text
.. 'resp'
or you can specify just a path:
.. doctest::
.. >>> adapter.register_uri('GET', '/path', text='resp')
.. >>> session.get('mock://test.com/path').text
.. 'resp'
.. >>> session.get('mock://another.com/path').text
.. 'resp'
Query Strings
=============
.. doctest::
:hide:
>>> import requests
>>> import requests_mock
>>> adapter = requests_mock.Adapter()
>>> session = requests.Session()
>>> session.mount('mock://', adapter)
Query strings provided to a register will match so long as at least those provided form part of the request.
.. doctest::
>>> adapter.register_uri('GET', '/7?a=1', text='resp')
>>> session.get('mock://test.com/7?a=1&b=2').text
'resp'
We can also match an empty query string.
.. doctest::
>>> adapter.register_uri('GET', '/7?a', text='resp')
>>> session.get('mock://test.com/7?a').text
'resp'
If any part of the query string is wrong then it will not match.
.. doctest::
>>> session.get('mock://test.com/7?a=3')
Traceback (most recent call last):
...
requests_mock.exceptions.NoMockAddress: No mock address: GET mock://test.com/7?a=3
This can be a problem in certain situations, so if you wish to match only the complete query string there is a flag `complete_qs`:
.. doctest::
>>> adapter.register_uri('GET', '/8?a=1', complete_qs=True, text='resp')
>>> session.get('mock://test.com/8?a=1&b=2')
Traceback (most recent call last):
...
requests_mock.exceptions.NoMockAddress: No mock address: GET mock://test.com/8?a=1&b=2
Matching ANY
============
There is a special symbol at `requests_mock.ANY` which acts as the wildcard to match anything.
It can be used as a replace for the method and/or the URL.
.. doctest::
:hide:
>>> import requests
>>> import requests_mock
>>> adapter = requests_mock.Adapter()
>>> session = requests.Session()
>>> session.mount('mock://', adapter)
.. doctest::
>>> adapter.register_uri(requests_mock.ANY, 'mock://test.com/8', text='resp')
>>> session.get('mock://test.com/8').text
'resp'
>>> session.post('mock://test.com/8').text
'resp'
.. doctest::
>>> adapter.register_uri(requests_mock.ANY, requests_mock.ANY, text='resp')
>>> session.get('mock://whatever/you/like').text
'resp'
>>> session.post('mock://whatever/you/like').text
'resp'
Regular Expressions
===================
URLs can be specified with a regular expression using the python :py:mod:`re` module.
To use this you should pass an object created by :py:func:`re.compile`.
The URL is then matched using :py:meth:`re.regex.search` which means that it will match any component of the url, so if you want to match the start of a URL you will have to anchor it.
.. doctest::
:hide:
>>> import requests
>>> import requests_mock
>>> adapter = requests_mock.Adapter()
>>> session = requests.Session()
>>> session.mount('mock://', adapter)
.. doctest::
.. >>> import re
.. >>> matcher = re.compile('tester.com/a')
.. >>> adapter.register_uri('GET', matcher, text='resp')
.. >>> session.get('mock://www.tester.com/a/b').text
.. 'resp'
If you use regular expression matching then *requests-mock* can't do its normal query string or path-only matching. That will need to be part of the expression.
Request Headers
===============
A dictionary of headers can be supplied such that the request will only match if the available headers also match.
Only the headers that are provided need match, any additional headers will be ignored.
.. doctest::
:hide:
>>> import requests
>>> import requests_mock
>>> adapter = requests_mock.Adapter()
>>> session = requests.Session()
>>> session.mount('mock://', adapter)
.. doctest::
>>> adapter.register_uri('POST', 'mock://test.com/headers', request_headers={'key': 'val'}, text='resp')
>>> session.post('mock://test.com/headers', headers={'key': 'val', 'another': 'header'}).text
'resp'
>>> resp = session.post('mock://test.com/headers')
Traceback (most recent call last):
...
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/additional', 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
===============
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 :py:const:`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 :py:const:`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
|