{"id":353,"date":"2017-06-20T16:32:38","date_gmt":"2017-06-20T16:32:38","guid":{"rendered":"http:\/\/library.brown.edu\/DigitalTechnologies\/?p=353"},"modified":"2017-06-20T16:32:38","modified_gmt":"2017-06-20T16:32:38","slug":"testing-http-calls-in-python","status":"publish","type":"post","link":"https:\/\/library.brown.edu\/create\/digitaltechnologies\/testing-http-calls-in-python\/","title":{"rendered":"Testing HTTP calls in Python"},"content":{"rendered":"<p>Many applications make calls to external services, or other services that are part of the application. Testing those HTTP calls can be challenging, but there are some different options available in Python.<\/p>\n<h3>Mocking<\/h3>\n<p>One option for testing your HTTP calls is to <a href=\"https:\/\/docs.python.org\/3\/library\/unittest.mock.html\">mock<\/a> out your function that makes the HTTP call. This way, your function doesn&#8217;t make the HTTP call, since it&#8217;s replaced by a mock function that just returns whatever you want it to.<\/p>\n<p>Here&#8217;s an example of mocking out your HTTP call:<\/p>\n<pre>import requests\n\nclass SomeClass:\n\n  def __init__(self):\n    self.data = self._fetch_data()\n\n  def _fetch_data(self):\n    r = requests.get('https:\/\/repository.library.brown.edu\/api\/collections\/')\n    return r.json()\n\n  def get_collection_ids(self):\n    return [c['id'] for c in self.data['collections']]\n\nfrom unittest.mock import patch\nMOCK_DATA = {'collections': [{'id': 1}, {'id': 2}]}\n\nwith patch.object(SomeClass, '_fetch_data', return_value=MOCK_DATA) as mock_method:\n  thing = SomeClass()\n  assert thing.get_collection_ids() == [1, 2]<\/pre>\n<p>Another mocking option is the <a href=\"https:\/\/pypi.python.org\/pypi\/responses\">responses<\/a> package. Responses mocks out the requests library specifically, so if you&#8217;re using requests, you can tell the responses package what you want each requests call to return.<\/p>\n<p>Here&#8217;s an example using the responses package (SomeClass is defined the same way as in the first example):<\/p>\n<pre>import responses\nimport json\nMOCK_JSON_DATA = json.dumps({'collections': [{'id': 1}, {'id': 2}]})\n\n@responses.activate\ndef test_some_class():\n  responses.add(responses.GET,  'https:\/\/repository.library.brown.edu\/api\/collections\/',\n body=MOCK_JSON_DATA,\n status=200,\n content_type='application\/json'\n )\n  thing = SomeClass()\n  assert thing.get_collection_ids() == [1, 2]\n\ntest_some_class()<\/pre>\n<h3>Record &amp; Replay Data<\/h3>\n<p>A different type of solution is to use a package to record the responses from your HTTP calls, and then replay those responses automatically for you.<\/p>\n<ul>\n<li><a href=\"https:\/\/pypi.python.org\/pypi\/vcrpy\">VCR.py<\/a> &#8211; VCR.py is a Python version of the Ruby <a href=\"https:\/\/github.com\/vcr\/vcr\">VCR<\/a> library, and it supports various HTTP clients, including requests.<\/li>\n<\/ul>\n<p>Here&#8217;s a VCR.py example, again using SomeClass from the first example:<\/p>\n<pre>import vcr \nIDS = [674, 278, 280, 282, 719, 300, 715, 659, 468, 720, 716, 687, 286, 288, 290, 296, 298, 671, 733, 672, 334, 328, 622, 318, 330, 332, 625, 740, 626, 336, 340, 338, 725, 724, 342, 549, 284, 457, 344, 346, 370, 350, 656, 352, 354, 356, 358, 406, 663, 710, 624, 362, 721, 700, 661, 364, 660, 718, 744, 702, 688, 366, 667]\n\nwith vcr.use_cassette('vcr_cassettes\/cassette.yaml'):\n  thing = SomeClass()\n  fetched_ids = thing.get_collection_ids()\n  assert sorted(fetched_ids) == sorted(IDS)<\/pre>\n<ul>\n<li><a href=\"https:\/\/betamax.readthedocs.io\/en\/latest\/\">betamax<\/a> &#8211; From the documentation: &#8220;Betamax is a <a class=\"reference external\" href=\"https:\/\/github.com\/vcr\/vcr\">VCR<\/a> imitation for requests.&#8221; Note that it is more limited than VCR.py, since it only works for the requests package.<\/li>\n<\/ul>\n<p>Here&#8217;s a betamax example (note: I modified the code in order to test it &#8211; maybe there&#8217;s a way to test the code with betamax without modifying it?):<\/p>\n<pre>import requests\n\nclass SomeClass:\n    def __init__(self, session=None):\n        self.data = self._fetch_data(session)\n\n    def _fetch_data(self, session=None):\n         if session:\n             r = session.get('https:\/\/repository.library.brown.edu\/api\/collections\/')\n         else:\n             r = requests.get('https:\/\/repository.library.brown.edu\/api\/collections\/')\n         return r.json()\n\n    def get_collection_ids(self):\n        return [c['id'] for c in self.data['collections']]\n\n\nimport betamax\nCASSETTE_LIBRARY_DIR = 'betamax_cassettes'\nIDS = [674, 278, 280, 282, 719, 300, 715, 659, 468, 720, 716, 687, 286, 288, 290, 296, 298, 671, 733, 672, 334, 328, 622, 318, 330, 332, 625, 740, 626, 336, 340, 338, 725, 724, 342, 549, 284, 457, 344, 346, 370, 350, 656, 352, 354, 356, 358, 406, 663, 710, 624, 362, 721, 700, 661, 364, 660, 718, 744, 702, 688, 366, 667]\n\nsession = requests.Session()\nrecorder = betamax.Betamax(\n session, cassette_library_dir=CASSETTE_LIBRARY_DIR\n )\n\nwith recorder.use_cassette('our-first-recorded-session', record='none'):\n    thing = SomeClass(session)\n    fetched_ids = thing.get_collection_ids()\n    assert sorted(fetched_ids) == sorted(IDS)<\/pre>\n<h3>Integration Test<\/h3>\n<p>Note that with all the solutions I listed above, it&#8217;s probably safest to cover the HTTP calls with an integration test that interacts with the real service, in addition to whatever you do in your unit tests.<\/p>\n<p>Another possible solution is to test as much as possible with unit tests without testing the HTTP call, and then just rely on the integration test(s) to test the HTTP call. If you&#8217;ve constructed your application so that the HTTP call is only a small, isolated part of the code, this may be a reasonable option.<\/p>\n<p>Here&#8217;s an example where the class fetches the data if needed, but the data can easily be put into the class for testing the rest of the functionality (without any mocking or external packages):<\/p>\n<pre>import requests\n\nclass SomeClass:\n\n    def __init__(self):\n        self._data = None\n\n    @property\n    def data(self):\n        if not self._data:\n            r = requests.get('https:\/\/repository.library.brown.edu\/api\/collections\/')\n            self._data = r.json()\n        return self._data\n\n    def get_collection_ids(self):\n        return [c['id'] for c in self.data['collections']]\n\n\nimport json\nMOCK_DATA = {'collections': [{'id': 1}, {'id': 2}]}\n\ndef test_some_class():\n    thing = SomeClass()\n    thing._data = MOCK_DATA\n    assert thing.get_collection_ids() == [1, 2]\n\ntest_some_class()<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Many applications make calls to external services, or other services that are part of the application. Testing those HTTP calls can be challenging, but there are some different options available in Python. Mocking One option for testing your HTTP calls is to mock out your function that makes the HTTP call. This way, your function &hellip; <a href=\"https:\/\/library.brown.edu\/create\/digitaltechnologies\/testing-http-calls-in-python\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Testing HTTP calls in Python<\/span><\/a><\/p>\n","protected":false},"author":110,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-353","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/library.brown.edu\/create\/digitaltechnologies\/wp-json\/wp\/v2\/posts\/353","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/library.brown.edu\/create\/digitaltechnologies\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/library.brown.edu\/create\/digitaltechnologies\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/library.brown.edu\/create\/digitaltechnologies\/wp-json\/wp\/v2\/users\/110"}],"replies":[{"embeddable":true,"href":"https:\/\/library.brown.edu\/create\/digitaltechnologies\/wp-json\/wp\/v2\/comments?post=353"}],"version-history":[{"count":0,"href":"https:\/\/library.brown.edu\/create\/digitaltechnologies\/wp-json\/wp\/v2\/posts\/353\/revisions"}],"wp:attachment":[{"href":"https:\/\/library.brown.edu\/create\/digitaltechnologies\/wp-json\/wp\/v2\/media?parent=353"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/library.brown.edu\/create\/digitaltechnologies\/wp-json\/wp\/v2\/categories?post=353"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/library.brown.edu\/create\/digitaltechnologies\/wp-json\/wp\/v2\/tags?post=353"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}