README.rst 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502
  1. Responses
  2. =========
  3. .. image:: https://img.shields.io/pypi/v/responses.svg
  4. :target: https://pypi.python.org/pypi/responses/
  5. .. image:: https://img.shields.io/pypi/pyversions/responses.svg
  6. :target: https://pypi.org/project/responses/
  7. .. image:: https://img.shields.io/pypi/dm/responses
  8. :target: https://pypi.python.org/pypi/responses/
  9. .. image:: https://codecov.io/gh/getsentry/responses/branch/master/graph/badge.svg
  10. :target: https://codecov.io/gh/getsentry/responses/
  11. A utility library for mocking out the ``requests`` Python library.
  12. .. note::
  13. Responses requires Python 3.8 or newer, and requests >= 2.30.0
  14. Table of Contents
  15. -----------------
  16. .. contents::
  17. Installing
  18. ----------
  19. ``pip install responses``
  20. Deprecations and Migration Path
  21. -------------------------------
  22. Here you will find a list of deprecated functionality and a migration path for each.
  23. Please ensure to update your code according to the guidance.
  24. .. list-table:: Deprecation and Migration
  25. :widths: 50 25 50
  26. :header-rows: 1
  27. * - Deprecated Functionality
  28. - Deprecated in Version
  29. - Migration Path
  30. * - ``responses.json_params_matcher``
  31. - 0.14.0
  32. - ``responses.matchers.json_params_matcher``
  33. * - ``responses.urlencoded_params_matcher``
  34. - 0.14.0
  35. - ``responses.matchers.urlencoded_params_matcher``
  36. * - ``stream`` argument in ``Response`` and ``CallbackResponse``
  37. - 0.15.0
  38. - Use ``stream`` argument in request directly.
  39. * - ``match_querystring`` argument in ``Response`` and ``CallbackResponse``.
  40. - 0.17.0
  41. - Use ``responses.matchers.query_param_matcher`` or ``responses.matchers.query_string_matcher``
  42. * - ``responses.assert_all_requests_are_fired``, ``responses.passthru_prefixes``, ``responses.target``
  43. - 0.20.0
  44. - Use ``responses.mock.assert_all_requests_are_fired``,
  45. ``responses.mock.passthru_prefixes``, ``responses.mock.target`` instead.
  46. BETA Features
  47. -------------
  48. Below you can find a list of BETA features. Although we will try to keep the API backwards compatible
  49. with released version, we reserve the right to change these APIs before they are considered stable. Please share your feedback via
  50. `GitHub Issues <https://github.com/getsentry/responses/issues>`_.
  51. Record Responses to files
  52. ^^^^^^^^^^^^^^^^^^^^^^^^^
  53. You can perform real requests to the server and ``responses`` will automatically record the output to the
  54. file. Recorded data is stored in `YAML <https://yaml.org>`_ format.
  55. Apply ``@responses._recorder.record(file_path="out.yaml")`` decorator to any function where you perform
  56. requests to record responses to ``out.yaml`` file.
  57. Following code
  58. .. code-block:: python
  59. import requests
  60. from responses import _recorder
  61. def another():
  62. rsp = requests.get("https://httpstat.us/500")
  63. rsp = requests.get("https://httpstat.us/202")
  64. @_recorder.record(file_path="out.yaml")
  65. def test_recorder():
  66. rsp = requests.get("https://httpstat.us/404")
  67. rsp = requests.get("https://httpbin.org/status/wrong")
  68. another()
  69. will produce next output:
  70. .. code-block:: yaml
  71. responses:
  72. - response:
  73. auto_calculate_content_length: false
  74. body: 404 Not Found
  75. content_type: text/plain
  76. method: GET
  77. status: 404
  78. url: https://httpstat.us/404
  79. - response:
  80. auto_calculate_content_length: false
  81. body: Invalid status code
  82. content_type: text/plain
  83. method: GET
  84. status: 400
  85. url: https://httpbin.org/status/wrong
  86. - response:
  87. auto_calculate_content_length: false
  88. body: 500 Internal Server Error
  89. content_type: text/plain
  90. method: GET
  91. status: 500
  92. url: https://httpstat.us/500
  93. - response:
  94. auto_calculate_content_length: false
  95. body: 202 Accepted
  96. content_type: text/plain
  97. method: GET
  98. status: 202
  99. url: https://httpstat.us/202
  100. Replay responses (populate registry) from files
  101. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  102. You can populate your active registry from a ``yaml`` file with recorded responses.
  103. (See `Record Responses to files`_ to understand how to obtain a file).
  104. To do that you need to execute ``responses._add_from_file(file_path="out.yaml")`` within
  105. an activated decorator or a context manager.
  106. The following code example registers a ``patch`` response, then all responses present in
  107. ``out.yaml`` file and a ``post`` response at the end.
  108. .. code-block:: python
  109. import responses
  110. @responses.activate
  111. def run():
  112. responses.patch("http://httpbin.org")
  113. responses._add_from_file(file_path="out.yaml")
  114. responses.post("http://httpbin.org/form")
  115. run()
  116. Basics
  117. ------
  118. The core of ``responses`` comes from registering mock responses and covering test function
  119. with ``responses.activate`` decorator. ``responses`` provides similar interface as ``requests``.
  120. Main Interface
  121. ^^^^^^^^^^^^^^
  122. * responses.add(``Response`` or ``Response args``) - allows either to register ``Response`` object or directly
  123. provide arguments of ``Response`` object. See `Response Parameters`_
  124. .. code-block:: python
  125. import responses
  126. import requests
  127. @responses.activate
  128. def test_simple():
  129. # Register via 'Response' object
  130. rsp1 = responses.Response(
  131. method="PUT",
  132. url="http://example.com",
  133. )
  134. responses.add(rsp1)
  135. # register via direct arguments
  136. responses.add(
  137. responses.GET,
  138. "http://twitter.com/api/1/foobar",
  139. json={"error": "not found"},
  140. status=404,
  141. )
  142. resp = requests.get("http://twitter.com/api/1/foobar")
  143. resp2 = requests.put("http://example.com")
  144. assert resp.json() == {"error": "not found"}
  145. assert resp.status_code == 404
  146. assert resp2.status_code == 200
  147. assert resp2.request.method == "PUT"
  148. If you attempt to fetch a url which doesn't hit a match, ``responses`` will raise
  149. a ``ConnectionError``:
  150. .. code-block:: python
  151. import responses
  152. import requests
  153. from requests.exceptions import ConnectionError
  154. @responses.activate
  155. def test_simple():
  156. with pytest.raises(ConnectionError):
  157. requests.get("http://twitter.com/api/1/foobar")
  158. Shortcuts
  159. ^^^^^^^^^
  160. Shortcuts provide a shorten version of ``responses.add()`` where method argument is prefilled
  161. * responses.delete(``Response args``) - register DELETE response
  162. * responses.get(``Response args``) - register GET response
  163. * responses.head(``Response args``) - register HEAD response
  164. * responses.options(``Response args``) - register OPTIONS response
  165. * responses.patch(``Response args``) - register PATCH response
  166. * responses.post(``Response args``) - register POST response
  167. * responses.put(``Response args``) - register PUT response
  168. .. code-block:: python
  169. import responses
  170. import requests
  171. @responses.activate
  172. def test_simple():
  173. responses.get(
  174. "http://twitter.com/api/1/foobar",
  175. json={"type": "get"},
  176. )
  177. responses.post(
  178. "http://twitter.com/api/1/foobar",
  179. json={"type": "post"},
  180. )
  181. responses.patch(
  182. "http://twitter.com/api/1/foobar",
  183. json={"type": "patch"},
  184. )
  185. resp_get = requests.get("http://twitter.com/api/1/foobar")
  186. resp_post = requests.post("http://twitter.com/api/1/foobar")
  187. resp_patch = requests.patch("http://twitter.com/api/1/foobar")
  188. assert resp_get.json() == {"type": "get"}
  189. assert resp_post.json() == {"type": "post"}
  190. assert resp_patch.json() == {"type": "patch"}
  191. Responses as a context manager
  192. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  193. Instead of wrapping the whole function with decorator you can use a context manager.
  194. .. code-block:: python
  195. import responses
  196. import requests
  197. def test_my_api():
  198. with responses.RequestsMock() as rsps:
  199. rsps.add(
  200. responses.GET,
  201. "http://twitter.com/api/1/foobar",
  202. body="{}",
  203. status=200,
  204. content_type="application/json",
  205. )
  206. resp = requests.get("http://twitter.com/api/1/foobar")
  207. assert resp.status_code == 200
  208. # outside the context manager requests will hit the remote server
  209. resp = requests.get("http://twitter.com/api/1/foobar")
  210. resp.status_code == 404
  211. Response Parameters
  212. -------------------
  213. The following attributes can be passed to a Response mock:
  214. method (``str``)
  215. The HTTP method (GET, POST, etc).
  216. url (``str`` or ``compiled regular expression``)
  217. The full resource URL.
  218. match_querystring (``bool``)
  219. DEPRECATED: Use ``responses.matchers.query_param_matcher`` or
  220. ``responses.matchers.query_string_matcher``
  221. Include the query string when matching requests.
  222. Enabled by default if the response URL contains a query string,
  223. disabled if it doesn't or the URL is a regular expression.
  224. body (``str`` or ``BufferedReader`` or ``Exception``)
  225. The response body. Read more `Exception as Response body`_
  226. json
  227. A Python object representing the JSON response body. Automatically configures
  228. the appropriate Content-Type.
  229. status (``int``)
  230. The HTTP status code.
  231. content_type (``content_type``)
  232. Defaults to ``text/plain``.
  233. headers (``dict``)
  234. Response headers.
  235. stream (``bool``)
  236. DEPRECATED: use ``stream`` argument in request directly
  237. auto_calculate_content_length (``bool``)
  238. Disabled by default. Automatically calculates the length of a supplied string or JSON body.
  239. match (``tuple``)
  240. An iterable (``tuple`` is recommended) of callbacks to match requests
  241. based on request attributes.
  242. Current module provides multiple matchers that you can use to match:
  243. * body contents in JSON format
  244. * body contents in URL encoded data format
  245. * request query parameters
  246. * request query string (similar to query parameters but takes string as input)
  247. * kwargs provided to request e.g. ``stream``, ``verify``
  248. * 'multipart/form-data' content and headers in request
  249. * request headers
  250. * request fragment identifier
  251. Alternatively user can create custom matcher.
  252. Read more `Matching Requests`_
  253. Exception as Response body
  254. --------------------------
  255. You can pass an ``Exception`` as the body to trigger an error on the request:
  256. .. code-block:: python
  257. import responses
  258. import requests
  259. @responses.activate
  260. def test_simple():
  261. responses.get("http://twitter.com/api/1/foobar", body=Exception("..."))
  262. with pytest.raises(Exception):
  263. requests.get("http://twitter.com/api/1/foobar")
  264. Matching Requests
  265. -----------------
  266. Matching Request Body Contents
  267. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  268. When adding responses for endpoints that are sent request data you can add
  269. matchers to ensure your code is sending the right parameters and provide
  270. different responses based on the request body contents. ``responses`` provides
  271. matchers for JSON and URL-encoded request bodies.
  272. URL-encoded data
  273. """"""""""""""""
  274. .. code-block:: python
  275. import responses
  276. import requests
  277. from responses import matchers
  278. @responses.activate
  279. def test_calc_api():
  280. responses.post(
  281. url="http://calc.com/sum",
  282. body="4",
  283. match=[matchers.urlencoded_params_matcher({"left": "1", "right": "3"})],
  284. )
  285. requests.post("http://calc.com/sum", data={"left": 1, "right": 3})
  286. JSON encoded data
  287. """""""""""""""""
  288. Matching JSON encoded data can be done with ``matchers.json_params_matcher()``.
  289. .. code-block:: python
  290. import responses
  291. import requests
  292. from responses import matchers
  293. @responses.activate
  294. def test_calc_api():
  295. responses.post(
  296. url="http://example.com/",
  297. body="one",
  298. match=[
  299. matchers.json_params_matcher({"page": {"name": "first", "type": "json"}})
  300. ],
  301. )
  302. resp = requests.request(
  303. "POST",
  304. "http://example.com/",
  305. headers={"Content-Type": "application/json"},
  306. json={"page": {"name": "first", "type": "json"}},
  307. )
  308. Query Parameters Matcher
  309. ^^^^^^^^^^^^^^^^^^^^^^^^
  310. Query Parameters as a Dictionary
  311. """"""""""""""""""""""""""""""""
  312. You can use the ``matchers.query_param_matcher`` function to match
  313. against the ``params`` request parameter. Just use the same dictionary as you
  314. will use in ``params`` argument in ``request``.
  315. Note, do not use query parameters as part of the URL. Avoid using ``match_querystring``
  316. deprecated argument.
  317. .. code-block:: python
  318. import responses
  319. import requests
  320. from responses import matchers
  321. @responses.activate
  322. def test_calc_api():
  323. url = "http://example.com/test"
  324. params = {"hello": "world", "I am": "a big test"}
  325. responses.get(
  326. url=url,
  327. body="test",
  328. match=[matchers.query_param_matcher(params)],
  329. )
  330. resp = requests.get(url, params=params)
  331. constructed_url = r"http://example.com/test?I+am=a+big+test&hello=world"
  332. assert resp.url == constructed_url
  333. assert resp.request.url == constructed_url
  334. assert resp.request.params == params
  335. By default, matcher will validate that all parameters match strictly.
  336. To validate that only parameters specified in the matcher are present in original request
  337. use ``strict_match=False``.
  338. Query Parameters as a String
  339. """"""""""""""""""""""""""""
  340. As alternative, you can use query string value in ``matchers.query_string_matcher`` to match
  341. query parameters in your request
  342. .. code-block:: python
  343. import requests
  344. import responses
  345. from responses import matchers
  346. @responses.activate
  347. def my_func():
  348. responses.get(
  349. "https://httpbin.org/get",
  350. match=[matchers.query_string_matcher("didi=pro&test=1")],
  351. )
  352. resp = requests.get("https://httpbin.org/get", params={"test": 1, "didi": "pro"})
  353. my_func()
  354. Request Keyword Arguments Matcher
  355. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  356. To validate request arguments use the ``matchers.request_kwargs_matcher`` function to match
  357. against the request kwargs.
  358. Only following arguments are supported: ``timeout``, ``verify``, ``proxies``, ``stream``, ``cert``.
  359. Note, only arguments provided to ``matchers.request_kwargs_matcher`` will be validated.
  360. .. code-block:: python
  361. import responses
  362. import requests
  363. from responses import matchers
  364. with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
  365. req_kwargs = {
  366. "stream": True,
  367. "verify": False,
  368. }
  369. rsps.add(
  370. "GET",
  371. "http://111.com",
  372. match=[matchers.request_kwargs_matcher(req_kwargs)],
  373. )
  374. requests.get("http://111.com", stream=True)
  375. # >>> Arguments don't match: {stream: True, verify: True} doesn't match {stream: True, verify: False}
  376. Request multipart/form-data Data Validation
  377. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  378. To validate request body and headers for ``multipart/form-data`` data you can use
  379. ``matchers.multipart_matcher``. The ``data``, and ``files`` parameters provided will be compared
  380. to the request:
  381. .. code-block:: python
  382. import requests
  383. import responses
  384. from responses.matchers import multipart_matcher
  385. @responses.activate
  386. def my_func():
  387. req_data = {"some": "other", "data": "fields"}
  388. req_files = {"file_name": b"Old World!"}
  389. responses.post(
  390. url="http://httpbin.org/post",
  391. match=[multipart_matcher(req_files, data=req_data)],
  392. )
  393. resp = requests.post("http://httpbin.org/post", files={"file_name": b"New World!"})
  394. my_func()
  395. # >>> raises ConnectionError: multipart/form-data doesn't match. Request body differs.
  396. Request Fragment Identifier Validation
  397. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  398. To validate request URL fragment identifier you can use ``matchers.fragment_identifier_matcher``.
  399. The matcher takes fragment string (everything after ``#`` sign) as input for comparison:
  400. .. code-block:: python
  401. import requests
  402. import responses
  403. from responses.matchers import fragment_identifier_matcher
  404. @responses.activate
  405. def run():
  406. url = "http://example.com?ab=xy&zed=qwe#test=1&foo=bar"
  407. responses.get(
  408. url,
  409. match=[fragment_identifier_matcher("test=1&foo=bar")],
  410. body=b"test",
  411. )
  412. # two requests to check reversed order of fragment identifier
  413. resp = requests.get("http://example.com?ab=xy&zed=qwe#test=1&foo=bar")
  414. resp = requests.get("http://example.com?zed=qwe&ab=xy#foo=bar&test=1")
  415. run()
  416. Request Headers Validation
  417. ^^^^^^^^^^^^^^^^^^^^^^^^^^
  418. When adding responses you can specify matchers to ensure that your code is
  419. sending the right headers and provide different responses based on the request
  420. headers.
  421. .. code-block:: python
  422. import responses
  423. import requests
  424. from responses import matchers
  425. @responses.activate
  426. def test_content_type():
  427. responses.get(
  428. url="http://example.com/",
  429. body="hello world",
  430. match=[matchers.header_matcher({"Accept": "text/plain"})],
  431. )
  432. responses.get(
  433. url="http://example.com/",
  434. json={"content": "hello world"},
  435. match=[matchers.header_matcher({"Accept": "application/json"})],
  436. )
  437. # request in reverse order to how they were added!
  438. resp = requests.get("http://example.com/", headers={"Accept": "application/json"})
  439. assert resp.json() == {"content": "hello world"}
  440. resp = requests.get("http://example.com/", headers={"Accept": "text/plain"})
  441. assert resp.text == "hello world"
  442. Because ``requests`` will send several standard headers in addition to what was
  443. specified by your code, request headers that are additional to the ones
  444. passed to the matcher are ignored by default. You can change this behaviour by
  445. passing ``strict_match=True`` to the matcher to ensure that only the headers
  446. that you're expecting are sent and no others. Note that you will probably have
  447. to use a ``PreparedRequest`` in your code to ensure that ``requests`` doesn't
  448. include any additional headers.
  449. .. code-block:: python
  450. import responses
  451. import requests
  452. from responses import matchers
  453. @responses.activate
  454. def test_content_type():
  455. responses.get(
  456. url="http://example.com/",
  457. body="hello world",
  458. match=[matchers.header_matcher({"Accept": "text/plain"}, strict_match=True)],
  459. )
  460. # this will fail because requests adds its own headers
  461. with pytest.raises(ConnectionError):
  462. requests.get("http://example.com/", headers={"Accept": "text/plain"})
  463. # a prepared request where you overwrite the headers before sending will work
  464. session = requests.Session()
  465. prepped = session.prepare_request(
  466. requests.Request(
  467. method="GET",
  468. url="http://example.com/",
  469. )
  470. )
  471. prepped.headers = {"Accept": "text/plain"}
  472. resp = session.send(prepped)
  473. assert resp.text == "hello world"
  474. Creating Custom Matcher
  475. ^^^^^^^^^^^^^^^^^^^^^^^
  476. If your application requires other encodings or different data validation you can build
  477. your own matcher that returns ``Tuple[matches: bool, reason: str]``.
  478. Where boolean represents ``True`` or ``False`` if the request parameters match and
  479. the string is a reason in case of match failure. Your matcher can
  480. expect a ``PreparedRequest`` parameter to be provided by ``responses``.
  481. Note, ``PreparedRequest`` is customized and has additional attributes ``params`` and ``req_kwargs``.
  482. Response Registry
  483. ---------------------------
  484. Default Registry
  485. ^^^^^^^^^^^^^^^^
  486. By default, ``responses`` will search all registered ``Response`` objects and
  487. return a match. If only one ``Response`` is registered, the registry is kept unchanged.
  488. However, if multiple matches are found for the same request, then first match is returned and
  489. removed from registry.
  490. Ordered Registry
  491. ^^^^^^^^^^^^^^^^
  492. In some scenarios it is important to preserve the order of the requests and responses.
  493. You can use ``registries.OrderedRegistry`` to force all ``Response`` objects to be dependent
  494. on the insertion order and invocation index.
  495. In following example we add multiple ``Response`` objects that target the same URL. However,
  496. you can see, that status code will depend on the invocation order.
  497. .. code-block:: python
  498. import requests
  499. import responses
  500. from responses.registries import OrderedRegistry
  501. @responses.activate(registry=OrderedRegistry)
  502. def test_invocation_index():
  503. responses.get(
  504. "http://twitter.com/api/1/foobar",
  505. json={"msg": "not found"},
  506. status=404,
  507. )
  508. responses.get(
  509. "http://twitter.com/api/1/foobar",
  510. json={"msg": "OK"},
  511. status=200,
  512. )
  513. responses.get(
  514. "http://twitter.com/api/1/foobar",
  515. json={"msg": "OK"},
  516. status=200,
  517. )
  518. responses.get(
  519. "http://twitter.com/api/1/foobar",
  520. json={"msg": "not found"},
  521. status=404,
  522. )
  523. resp = requests.get("http://twitter.com/api/1/foobar")
  524. assert resp.status_code == 404
  525. resp = requests.get("http://twitter.com/api/1/foobar")
  526. assert resp.status_code == 200
  527. resp = requests.get("http://twitter.com/api/1/foobar")
  528. assert resp.status_code == 200
  529. resp = requests.get("http://twitter.com/api/1/foobar")
  530. assert resp.status_code == 404
  531. Custom Registry
  532. ^^^^^^^^^^^^^^^
  533. Built-in ``registries`` are suitable for most of use cases, but to handle special conditions, you can
  534. implement custom registry which must follow interface of ``registries.FirstMatchRegistry``.
  535. Redefining the ``find`` method will allow you to create custom search logic and return
  536. appropriate ``Response``
  537. Example that shows how to set custom registry
  538. .. code-block:: python
  539. import responses
  540. from responses import registries
  541. class CustomRegistry(registries.FirstMatchRegistry):
  542. pass
  543. print("Before tests:", responses.mock.get_registry())
  544. """ Before tests: <responses.registries.FirstMatchRegistry object> """
  545. # using function decorator
  546. @responses.activate(registry=CustomRegistry)
  547. def run():
  548. print("Within test:", responses.mock.get_registry())
  549. """ Within test: <__main__.CustomRegistry object> """
  550. run()
  551. print("After test:", responses.mock.get_registry())
  552. """ After test: <responses.registries.FirstMatchRegistry object> """
  553. # using context manager
  554. with responses.RequestsMock(registry=CustomRegistry) as rsps:
  555. print("In context manager:", rsps.get_registry())
  556. """ In context manager: <__main__.CustomRegistry object> """
  557. print("After exit from context manager:", responses.mock.get_registry())
  558. """
  559. After exit from context manager: <responses.registries.FirstMatchRegistry object>
  560. """
  561. Dynamic Responses
  562. -----------------
  563. You can utilize callbacks to provide dynamic responses. The callback must return
  564. a tuple of (``status``, ``headers``, ``body``).
  565. .. code-block:: python
  566. import json
  567. import responses
  568. import requests
  569. @responses.activate
  570. def test_calc_api():
  571. def request_callback(request):
  572. payload = json.loads(request.body)
  573. resp_body = {"value": sum(payload["numbers"])}
  574. headers = {"request-id": "728d329e-0e86-11e4-a748-0c84dc037c13"}
  575. return (200, headers, json.dumps(resp_body))
  576. responses.add_callback(
  577. responses.POST,
  578. "http://calc.com/sum",
  579. callback=request_callback,
  580. content_type="application/json",
  581. )
  582. resp = requests.post(
  583. "http://calc.com/sum",
  584. json.dumps({"numbers": [1, 2, 3]}),
  585. headers={"content-type": "application/json"},
  586. )
  587. assert resp.json() == {"value": 6}
  588. assert len(responses.calls) == 1
  589. assert responses.calls[0].request.url == "http://calc.com/sum"
  590. assert responses.calls[0].response.text == '{"value": 6}'
  591. assert (
  592. responses.calls[0].response.headers["request-id"]
  593. == "728d329e-0e86-11e4-a748-0c84dc037c13"
  594. )
  595. You can also pass a compiled regex to ``add_callback`` to match multiple urls:
  596. .. code-block:: python
  597. import re, json
  598. from functools import reduce
  599. import responses
  600. import requests
  601. operators = {
  602. "sum": lambda x, y: x + y,
  603. "prod": lambda x, y: x * y,
  604. "pow": lambda x, y: x**y,
  605. }
  606. @responses.activate
  607. def test_regex_url():
  608. def request_callback(request):
  609. payload = json.loads(request.body)
  610. operator_name = request.path_url[1:]
  611. operator = operators[operator_name]
  612. resp_body = {"value": reduce(operator, payload["numbers"])}
  613. headers = {"request-id": "728d329e-0e86-11e4-a748-0c84dc037c13"}
  614. return (200, headers, json.dumps(resp_body))
  615. responses.add_callback(
  616. responses.POST,
  617. re.compile("http://calc.com/(sum|prod|pow|unsupported)"),
  618. callback=request_callback,
  619. content_type="application/json",
  620. )
  621. resp = requests.post(
  622. "http://calc.com/prod",
  623. json.dumps({"numbers": [2, 3, 4]}),
  624. headers={"content-type": "application/json"},
  625. )
  626. assert resp.json() == {"value": 24}
  627. test_regex_url()
  628. If you want to pass extra keyword arguments to the callback function, for example when reusing
  629. a callback function to give a slightly different result, you can use ``functools.partial``:
  630. .. code-block:: python
  631. from functools import partial
  632. def request_callback(request, id=None):
  633. payload = json.loads(request.body)
  634. resp_body = {"value": sum(payload["numbers"])}
  635. headers = {"request-id": id}
  636. return (200, headers, json.dumps(resp_body))
  637. responses.add_callback(
  638. responses.POST,
  639. "http://calc.com/sum",
  640. callback=partial(request_callback, id="728d329e-0e86-11e4-a748-0c84dc037c13"),
  641. content_type="application/json",
  642. )
  643. Integration with unit test frameworks
  644. -------------------------------------
  645. Responses as a ``pytest`` fixture
  646. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  647. .. code-block:: python
  648. @pytest.fixture
  649. def mocked_responses():
  650. with responses.RequestsMock() as rsps:
  651. yield rsps
  652. def test_api(mocked_responses):
  653. mocked_responses.get(
  654. "http://twitter.com/api/1/foobar",
  655. body="{}",
  656. status=200,
  657. content_type="application/json",
  658. )
  659. resp = requests.get("http://twitter.com/api/1/foobar")
  660. assert resp.status_code == 200
  661. Add default responses for each test
  662. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  663. When run with ``unittest`` tests, this can be used to set up some
  664. generic class-level responses, that may be complemented by each test.
  665. Similar interface could be applied in ``pytest`` framework.
  666. .. code-block:: python
  667. class TestMyApi(unittest.TestCase):
  668. def setUp(self):
  669. responses.get("https://example.com", body="within setup")
  670. # here go other self.responses.add(...)
  671. @responses.activate
  672. def test_my_func(self):
  673. responses.get(
  674. "https://httpbin.org/get",
  675. match=[matchers.query_param_matcher({"test": "1", "didi": "pro"})],
  676. body="within test",
  677. )
  678. resp = requests.get("https://example.com")
  679. resp2 = requests.get(
  680. "https://httpbin.org/get", params={"test": "1", "didi": "pro"}
  681. )
  682. print(resp.text)
  683. # >>> within setup
  684. print(resp2.text)
  685. # >>> within test
  686. RequestMock methods: start, stop, reset
  687. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  688. ``responses`` has ``start``, ``stop``, ``reset`` methods very analogous to
  689. `unittest.mock.patch <https://docs.python.org/3/library/unittest.mock.html#patch-methods-start-and-stop>`_.
  690. These make it simpler to do requests mocking in ``setup`` methods or where
  691. you want to do multiple patches without nesting decorators or with statements.
  692. .. code-block:: python
  693. class TestUnitTestPatchSetup:
  694. def setup(self):
  695. """Creates ``RequestsMock`` instance and starts it."""
  696. self.r_mock = responses.RequestsMock(assert_all_requests_are_fired=True)
  697. self.r_mock.start()
  698. # optionally some default responses could be registered
  699. self.r_mock.get("https://example.com", status=505)
  700. self.r_mock.put("https://example.com", status=506)
  701. def teardown(self):
  702. """Stops and resets RequestsMock instance.
  703. If ``assert_all_requests_are_fired`` is set to ``True``, will raise an error
  704. if some requests were not processed.
  705. """
  706. self.r_mock.stop()
  707. self.r_mock.reset()
  708. def test_function(self):
  709. resp = requests.get("https://example.com")
  710. assert resp.status_code == 505
  711. resp = requests.put("https://example.com")
  712. assert resp.status_code == 506
  713. Assertions on declared responses
  714. --------------------------------
  715. When used as a context manager, Responses will, by default, raise an assertion
  716. error if a url was registered but not accessed. This can be disabled by passing
  717. the ``assert_all_requests_are_fired`` value:
  718. .. code-block:: python
  719. import responses
  720. import requests
  721. def test_my_api():
  722. with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
  723. rsps.add(
  724. responses.GET,
  725. "http://twitter.com/api/1/foobar",
  726. body="{}",
  727. status=200,
  728. content_type="application/json",
  729. )
  730. Assert Request Call Count
  731. -------------------------
  732. Assert based on ``Response`` object
  733. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  734. Each ``Response`` object has ``call_count`` attribute that could be inspected
  735. to check how many times each request was matched.
  736. .. code-block:: python
  737. @responses.activate
  738. def test_call_count_with_matcher():
  739. rsp = responses.get(
  740. "http://www.example.com",
  741. match=(matchers.query_param_matcher({}),),
  742. )
  743. rsp2 = responses.get(
  744. "http://www.example.com",
  745. match=(matchers.query_param_matcher({"hello": "world"}),),
  746. status=777,
  747. )
  748. requests.get("http://www.example.com")
  749. resp1 = requests.get("http://www.example.com")
  750. requests.get("http://www.example.com?hello=world")
  751. resp2 = requests.get("http://www.example.com?hello=world")
  752. assert resp1.status_code == 200
  753. assert resp2.status_code == 777
  754. assert rsp.call_count == 2
  755. assert rsp2.call_count == 2
  756. Assert based on the exact URL
  757. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  758. Assert that the request was called exactly n times.
  759. .. code-block:: python
  760. import responses
  761. import requests
  762. @responses.activate
  763. def test_assert_call_count():
  764. responses.get("http://example.com")
  765. requests.get("http://example.com")
  766. assert responses.assert_call_count("http://example.com", 1) is True
  767. requests.get("http://example.com")
  768. with pytest.raises(AssertionError) as excinfo:
  769. responses.assert_call_count("http://example.com", 1)
  770. assert (
  771. "Expected URL 'http://example.com' to be called 1 times. Called 2 times."
  772. in str(excinfo.value)
  773. )
  774. @responses.activate
  775. def test_assert_call_count_always_match_qs():
  776. responses.get("http://www.example.com")
  777. requests.get("http://www.example.com")
  778. requests.get("http://www.example.com?hello=world")
  779. # One call on each url, querystring is matched by default
  780. responses.assert_call_count("http://www.example.com", 1) is True
  781. responses.assert_call_count("http://www.example.com?hello=world", 1) is True
  782. Assert Request Calls data
  783. -------------------------
  784. ``Request`` object has ``calls`` list which elements correspond to ``Call`` objects
  785. in the global list of ``Registry``. This can be useful when the order of requests is not
  786. guaranteed, but you need to check their correctness, for example in multithreaded
  787. applications.
  788. .. code-block:: python
  789. import concurrent.futures
  790. import responses
  791. import requests
  792. @responses.activate
  793. def test_assert_calls_on_resp():
  794. rsp1 = responses.patch("http://www.foo.bar/1/", status=200)
  795. rsp2 = responses.patch("http://www.foo.bar/2/", status=400)
  796. rsp3 = responses.patch("http://www.foo.bar/3/", status=200)
  797. def update_user(uid, is_active):
  798. url = f"http://www.foo.bar/{uid}/"
  799. response = requests.patch(url, json={"is_active": is_active})
  800. return response
  801. with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
  802. future_to_uid = {
  803. executor.submit(update_user, uid, is_active): uid
  804. for (uid, is_active) in [("3", True), ("2", True), ("1", False)]
  805. }
  806. for future in concurrent.futures.as_completed(future_to_uid):
  807. uid = future_to_uid[future]
  808. response = future.result()
  809. print(f"{uid} updated with {response.status_code} status code")
  810. assert len(responses.calls) == 3 # total calls count
  811. assert rsp1.call_count == 1
  812. assert rsp1.calls[0] in responses.calls
  813. assert rsp1.calls[0].response.status_code == 200
  814. assert json.loads(rsp1.calls[0].request.body) == {"is_active": False}
  815. assert rsp2.call_count == 1
  816. assert rsp2.calls[0] in responses.calls
  817. assert rsp2.calls[0].response.status_code == 400
  818. assert json.loads(rsp2.calls[0].request.body) == {"is_active": True}
  819. assert rsp3.call_count == 1
  820. assert rsp3.calls[0] in responses.calls
  821. assert rsp3.calls[0].response.status_code == 200
  822. assert json.loads(rsp3.calls[0].request.body) == {"is_active": True}
  823. Multiple Responses
  824. ------------------
  825. You can also add multiple responses for the same url:
  826. .. code-block:: python
  827. import responses
  828. import requests
  829. @responses.activate
  830. def test_my_api():
  831. responses.get("http://twitter.com/api/1/foobar", status=500)
  832. responses.get(
  833. "http://twitter.com/api/1/foobar",
  834. body="{}",
  835. status=200,
  836. content_type="application/json",
  837. )
  838. resp = requests.get("http://twitter.com/api/1/foobar")
  839. assert resp.status_code == 500
  840. resp = requests.get("http://twitter.com/api/1/foobar")
  841. assert resp.status_code == 200
  842. URL Redirection
  843. ---------------
  844. In the following example you can see how to create a redirection chain and add custom exception that will be raised
  845. in the execution chain and contain the history of redirects.
  846. .. code-block::
  847. A -> 301 redirect -> B
  848. B -> 301 redirect -> C
  849. C -> connection issue
  850. .. code-block:: python
  851. import pytest
  852. import requests
  853. import responses
  854. @responses.activate
  855. def test_redirect():
  856. # create multiple Response objects where first two contain redirect headers
  857. rsp1 = responses.Response(
  858. responses.GET,
  859. "http://example.com/1",
  860. status=301,
  861. headers={"Location": "http://example.com/2"},
  862. )
  863. rsp2 = responses.Response(
  864. responses.GET,
  865. "http://example.com/2",
  866. status=301,
  867. headers={"Location": "http://example.com/3"},
  868. )
  869. rsp3 = responses.Response(responses.GET, "http://example.com/3", status=200)
  870. # register above generated Responses in ``response`` module
  871. responses.add(rsp1)
  872. responses.add(rsp2)
  873. responses.add(rsp3)
  874. # do the first request in order to generate genuine ``requests`` response
  875. # this object will contain genuine attributes of the response, like ``history``
  876. rsp = requests.get("http://example.com/1")
  877. responses.calls.reset()
  878. # customize exception with ``response`` attribute
  879. my_error = requests.ConnectionError("custom error")
  880. my_error.response = rsp
  881. # update body of the 3rd response with Exception, this will be raised during execution
  882. rsp3.body = my_error
  883. with pytest.raises(requests.ConnectionError) as exc_info:
  884. requests.get("http://example.com/1")
  885. assert exc_info.value.args[0] == "custom error"
  886. assert rsp1.url in exc_info.value.response.history[0].url
  887. assert rsp2.url in exc_info.value.response.history[1].url
  888. Validate ``Retry`` mechanism
  889. ----------------------------
  890. If you are using the ``Retry`` features of ``urllib3`` and want to cover scenarios that test your retry limits, you can test those scenarios with ``responses`` as well. The best approach will be to use an `Ordered Registry`_
  891. .. code-block:: python
  892. import requests
  893. import responses
  894. from responses import registries
  895. from urllib3.util import Retry
  896. @responses.activate(registry=registries.OrderedRegistry)
  897. def test_max_retries():
  898. url = "https://example.com"
  899. rsp1 = responses.get(url, body="Error", status=500)
  900. rsp2 = responses.get(url, body="Error", status=500)
  901. rsp3 = responses.get(url, body="Error", status=500)
  902. rsp4 = responses.get(url, body="OK", status=200)
  903. session = requests.Session()
  904. adapter = requests.adapters.HTTPAdapter(
  905. max_retries=Retry(
  906. total=4,
  907. backoff_factor=0.1,
  908. status_forcelist=[500],
  909. method_whitelist=["GET", "POST", "PATCH"],
  910. )
  911. )
  912. session.mount("https://", adapter)
  913. resp = session.get(url)
  914. assert resp.status_code == 200
  915. assert rsp1.call_count == 1
  916. assert rsp2.call_count == 1
  917. assert rsp3.call_count == 1
  918. assert rsp4.call_count == 1
  919. Using a callback to modify the response
  920. ---------------------------------------
  921. If you use customized processing in ``requests`` via subclassing/mixins, or if you
  922. have library tools that interact with ``requests`` at a low level, you may need
  923. to add extended processing to the mocked Response object to fully simulate the
  924. environment for your tests. A ``response_callback`` can be used, which will be
  925. wrapped by the library before being returned to the caller. The callback
  926. accepts a ``response`` as it's single argument, and is expected to return a
  927. single ``response`` object.
  928. .. code-block:: python
  929. import responses
  930. import requests
  931. def response_callback(resp):
  932. resp.callback_processed = True
  933. return resp
  934. with responses.RequestsMock(response_callback=response_callback) as m:
  935. m.add(responses.GET, "http://example.com", body=b"test")
  936. resp = requests.get("http://example.com")
  937. assert resp.text == "test"
  938. assert hasattr(resp, "callback_processed")
  939. assert resp.callback_processed is True
  940. Passing through real requests
  941. -----------------------------
  942. In some cases you may wish to allow for certain requests to pass through responses
  943. and hit a real server. This can be done with the ``add_passthru`` methods:
  944. .. code-block:: python
  945. import responses
  946. @responses.activate
  947. def test_my_api():
  948. responses.add_passthru("https://percy.io")
  949. This will allow any requests matching that prefix, that is otherwise not
  950. registered as a mock response, to passthru using the standard behavior.
  951. Pass through endpoints can be configured with regex patterns if you
  952. need to allow an entire domain or path subtree to send requests:
  953. .. code-block:: python
  954. responses.add_passthru(re.compile("https://percy.io/\\w+"))
  955. Lastly, you can use the ``passthrough`` argument of the ``Response`` object
  956. to force a response to behave as a pass through.
  957. .. code-block:: python
  958. # Enable passthrough for a single response
  959. response = Response(
  960. responses.GET,
  961. "http://example.com",
  962. body="not used",
  963. passthrough=True,
  964. )
  965. responses.add(response)
  966. # Use PassthroughResponse
  967. response = PassthroughResponse(responses.GET, "http://example.com")
  968. responses.add(response)
  969. Viewing/Modifying registered responses
  970. --------------------------------------
  971. Registered responses are available as a public method of the RequestMock
  972. instance. It is sometimes useful for debugging purposes to view the stack of
  973. registered responses which can be accessed via ``responses.registered()``.
  974. The ``replace`` function allows a previously registered ``response`` to be
  975. changed. The method signature is identical to ``add``. ``response`` s are
  976. identified using ``method`` and ``url``. Only the first matched ``response`` is
  977. replaced.
  978. .. code-block:: python
  979. import responses
  980. import requests
  981. @responses.activate
  982. def test_replace():
  983. responses.get("http://example.org", json={"data": 1})
  984. responses.replace(responses.GET, "http://example.org", json={"data": 2})
  985. resp = requests.get("http://example.org")
  986. assert resp.json() == {"data": 2}
  987. The ``upsert`` function allows a previously registered ``response`` to be
  988. changed like ``replace``. If the response is registered, the ``upsert`` function
  989. will registered it like ``add``.
  990. ``remove`` takes a ``method`` and ``url`` argument and will remove **all**
  991. matched responses from the registered list.
  992. Finally, ``reset`` will reset all registered responses.
  993. Coroutines and Multithreading
  994. -----------------------------
  995. ``responses`` supports both Coroutines and Multithreading out of the box.
  996. Note, ``responses`` locks threading on ``RequestMock`` object allowing only
  997. single thread to access it.
  998. .. code-block:: python
  999. async def test_async_calls():
  1000. @responses.activate
  1001. async def run():
  1002. responses.get(
  1003. "http://twitter.com/api/1/foobar",
  1004. json={"error": "not found"},
  1005. status=404,
  1006. )
  1007. resp = requests.get("http://twitter.com/api/1/foobar")
  1008. assert resp.json() == {"error": "not found"}
  1009. assert responses.calls[0].request.url == "http://twitter.com/api/1/foobar"
  1010. await run()
  1011. Contributing
  1012. ------------
  1013. Environment Configuration
  1014. ^^^^^^^^^^^^^^^^^^^^^^^^^
  1015. Responses uses several linting and autoformatting utilities, so it's important that when
  1016. submitting patches you use the appropriate toolchain:
  1017. Clone the repository:
  1018. .. code-block:: shell
  1019. git clone https://github.com/getsentry/responses.git
  1020. Create an environment (e.g. with ``virtualenv``):
  1021. .. code-block:: shell
  1022. virtualenv .env && source .env/bin/activate
  1023. Configure development requirements:
  1024. .. code-block:: shell
  1025. make develop
  1026. Tests and Code Quality Validation
  1027. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  1028. The easiest way to validate your code is to run tests via ``tox``.
  1029. Current ``tox`` configuration runs the same checks that are used in
  1030. GitHub Actions CI/CD pipeline.
  1031. Please execute the following command line from the project root to validate
  1032. your code against:
  1033. * Unit tests in all Python versions that are supported by this project
  1034. * Type validation via ``mypy``
  1035. * All ``pre-commit`` hooks
  1036. .. code-block:: shell
  1037. tox
  1038. Alternatively, you can always run a single test. See documentation below.
  1039. Unit tests
  1040. """"""""""
  1041. Responses uses `Pytest <https://docs.pytest.org/en/latest/>`_ for
  1042. testing. You can run all tests by:
  1043. .. code-block:: shell
  1044. tox -e py37
  1045. tox -e py310
  1046. OR manually activate required version of Python and run
  1047. .. code-block:: shell
  1048. pytest
  1049. And run a single test by:
  1050. .. code-block:: shell
  1051. pytest -k '<test_function_name>'
  1052. Type Validation
  1053. """""""""""""""
  1054. To verify ``type`` compliance, run `mypy <https://github.com/python/mypy>`_ linter:
  1055. .. code-block:: shell
  1056. tox -e mypy
  1057. OR
  1058. .. code-block:: shell
  1059. mypy --config-file=./mypy.ini -p responses
  1060. Code Quality and Style
  1061. """"""""""""""""""""""
  1062. To check code style and reformat it run:
  1063. .. code-block:: shell
  1064. tox -e precom
  1065. OR
  1066. .. code-block:: shell
  1067. pre-commit run --all-files