README.rst 44 KB

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