METADATA 46 KB

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