File: matching.rst

package info (click to toggle)
python-requests-mock 1.12.1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 656 kB
  • sloc: python: 2,339; makefile: 162
file content (288 lines) | stat: -rw-r--r-- 9,171 bytes parent folder | download
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