METADATA 46 KB

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