{
    "componentChunkName": "component---src-templates-post-page-js",
    "path": "/2018/testing-aiohttp-client",
    "result": {"data":{"site":{"siteMetadata":{"title":"Solid Abstractions","siteUrl":"https://solidabstractions.com","twitterId":291334023,"author":{"fullName":"Julien Hartmann","profileHtml":"I am an open-source de­vel­op­er, for­mer IT con­sul­tant with a pas­sion for new tech­nol­o­gies. I be­lieve the role of an en­gi­neer is to em­pow­er peo­ple, by as­sem­bling sim­ple, re­fined de­signs.\n","links":[{"url":"https://github.com/spectras/","name":"github","title":"GitHub"},{"url":"https://stackoverflow.com/users/3212865/spectras","name":"stackoverflow","title":"StackOverflow"},{"url":"https://www.linkedin.com/in/julienhartmann/","name":"linkedin","title":"LinkedIn"}],"profilePicNode":{"original":{"src":"/static/profile-pic-301a9cbe7b572c3e7910c9717d2b3bcd.jpg"}},"url":"https://etherdream.org/about"}}},"markdownRemark":{"id":"7b9125bb-dffa-5cdf-8139-abb5a2951dd7","excerpt":"Asynchronous code is the new paradigm in python those last years.\nTesting it, though, is significantly harder.\nLet's see how to test asynchronous HTTP client code written with aiohttp.","html":"<p>Asynchronous code is the new paradigm in python those last years.\nTesting it, though, is significantly harder.\nLet's see how to test asynchronous HTTP client code written with aiohttp.</p>\n<p>--- excerpt ---</p>\n<p>Asynchronous code is the new paradigm in python those last years. Leveraging coroutine\nsupport to write asynchronous code in a non-blocking fashion is very convenient.</p>\n<p>But it opens up a whole new uncharted territory. Testing, notably, is\nsignificantly harder: exceptions might go unnoticed, unit tests might end without\nwaiting for all asynchronous code to complete, and so on.</p>\n<p>It does, however, enable interesting new techniques for testing. In this article, we\nshall test asynchronous client-side code written using the amazing <a href=\"https://aiohttp.readthedocs.io/\" class=\"external\" rel=\"external noopener noreferrer\"><code class=\"language-text\">aiohttp</code></a>.\nWe will do this in two parts:</p>\n<blockquote>\n<ol>\n<li>Testing tools <em>(this post)</em>.</li>\n<li><a href=\"testing-aiohttp-client-part-2\" class=\"internal\">Actual implementation</a>.</li>\n</ol>\n</blockquote>\n<h2 id=\"objectives\">Objectives</h2>\n<h3 id=\"fetching-bitcoin-price\">Fetching bitcoin price</h3>\n<p>So here are our requirements: we would like to fetch the latest bitcoin price from\n<a href=\"https://www.kraken.com/help/api#get-ticker-info\" class=\"external\" rel=\"external noopener noreferrer\">Kraken</a> exchange, using the following\ninterface:</p>\n<div class=\"figure-wrapper\"><figure id=\"fig-1\"><div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">class</span> <span class=\"token class-name\">QuotationProvider</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">get_price</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">,</span> pair<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        <span class=\"token triple-quoted-string string\">''' Get latest trade price for given pair.\n        Args:\n            pair (str): name of a pair symbol on exchange.\n        Returns:\n            Decimal: latest price for the symbol.\n        Raises:\n            PayloadError: if server response could not be understood.\n            QuotationError: if quotation is not available.\n            ValueError: if `pair` is not a valid pair symbol.\n        '''</span>\n\n<span class=\"token keyword\">class</span> <span class=\"token class-name\">QuotationError</span><span class=\"token punctuation\">(</span>Exception<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span> <span class=\"token keyword\">pass</span>\n<span class=\"token keyword\">class</span> <span class=\"token class-name\">PayloadError</span><span class=\"token punctuation\">(</span>QuotationError<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span> <span class=\"token keyword\">pass</span></code></pre></div><figcaption><div class=\"figcaption-wrapper\">Interface definition</div></figcaption></figure></div>\n<p>Here is the <strong>expected use</strong>, assuming we built a <code class=\"language-text\">Kraken</code> class that implements\nthe above interface:</p>\n<div class=\"figure-wrapper\"><figure id=\"fig-2\"><div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token operator\">>></span><span class=\"token operator\">></span> xchg <span class=\"token operator\">=</span> Kraken<span class=\"token punctuation\">(</span>session<span class=\"token operator\">=</span>aiohttp<span class=\"token punctuation\">.</span>ClientSession<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n<span class=\"token operator\">>></span><span class=\"token operator\">></span> <span class=\"token keyword\">await</span> xchg<span class=\"token punctuation\">.</span>get_price<span class=\"token punctuation\">(</span><span class=\"token string\">'btcusd'</span><span class=\"token punctuation\">)</span>\nDecimal<span class=\"token punctuation\">(</span><span class=\"token string\">'3967.40000'</span><span class=\"token punctuation\">)</span></code></pre></div><figcaption><div class=\"figcaption-wrapper\">Example session using <a href=\"https://aioconsole.readthedocs.io\" class=\"external\" rel=\"external noopener noreferrer\">apython</a> interpreter.</div></figcaption></figure></div>\n<p>Simple enough, right? Now, before we dive into code, how do we ensure it works fine?\nOr:<br><b>How do we test it properly?</b></p>\n<h3 id=\"testing-goals\">Testing goals</h3>\n<p>Our final goal for this post is to unit-test this quotation client thoroughly and cleanly.\nWhat does that mean? It means we want our tests to be:</p>\n<dl><dt>Isolated</dt><dd><p>Testing must not depend on external resources. In particular, tests must <b>not\nperform actual requests</b> against Kraken's servers.</p></dd><dt>Faithful</dt><dd><p>Test cases should be as close as possible to <b>real-world conditions</b>. We want to\nstrive for behavioral testing, rather than checking implementation details.</p><p>This means we want to setup an actual HTTP server for the tests to interact with tested code,\nunder the control of the test case. The good news is with asyncio, we can actually run\none <em>from within the test</em>.</p></dd><dt>Comprehensive</dt><dd><p>In particular, we want to test <b>error conditions</b> and <b>security features</b>. Namely,\nwe want to test that we will not accept data from a server that has an invalid SSL\ncertificate. This implies our tests <strong>cannot circumvent the SSL layer</strong>.</p></dd><dt>Unintrusive</dt><dd><p>We want to test the <b>actual code</b>, not modify or patch it for testing, as this could\nhide bugs. In particular, if the client code uses URIs, we are not allowed to\nreach in and alter them.</p></dd></dl>\n<p>Lastly, we will aim at making tests as <b>convenient</b> to write as possible. Most developers\nhate writing tests, and lowering the barrier is a reasonable way to encourage a\nmore comprehensive test suite.</p>\n<h2 id=\"test-support-tools\">Test support tools</h2>\n<h3 id=\"checking-out-included-batteries\">Checking out included batteries</h3>\n<p>The aiohttp library provides a basic\n<a href=\"https://aiohttp.readthedocs.io/en/stable/testing.html#testing-api-reference\" class=\"external\" rel=\"external noopener noreferrer\">testing toolset</a>.\nIt allows setting up a test webapp, creating a server instance to serve it, and optionally\nrunning it asynchronously in the background. For instance:</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token decorator annotation punctuation\">@pytest<span class=\"token punctuation\">.</span>mark<span class=\"token punctuation\">.</span>asyncio</span>\n<span class=\"token keyword\">def</span> <span class=\"token function\">test_example</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    app <span class=\"token operator\">=</span> aiohttp<span class=\"token punctuation\">.</span>web<span class=\"token punctuation\">.</span>Application<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">handler</span><span class=\"token punctuation\">(</span>request<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">return</span> aiohttp<span class=\"token punctuation\">.</span>web<span class=\"token punctuation\">.</span>Response<span class=\"token punctuation\">(</span>text<span class=\"token operator\">=</span>SAMPLE_RESPONSE_FROM_KRAKEN<span class=\"token punctuation\">)</span>\n    app<span class=\"token punctuation\">.</span>router<span class=\"token punctuation\">.</span>add_route<span class=\"token punctuation\">(</span><span class=\"token string\">'get'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'/0/public/Ticker'</span><span class=\"token punctuation\">,</span> handler<span class=\"token punctuation\">)</span>\n\n    server <span class=\"token operator\">=</span> aiohttp<span class=\"token punctuation\">.</span>test_utils<span class=\"token punctuation\">.</span>TestServer<span class=\"token punctuation\">(</span>app<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">async</span> <span class=\"token keyword\">with</span> server<span class=\"token punctuation\">:</span>\n        kraken <span class=\"token operator\">=</span> Kraken<span class=\"token punctuation\">(</span>server<span class=\"token operator\">=</span><span class=\"token string\">'localhost'</span><span class=\"token punctuation\">,</span> port<span class=\"token operator\">=</span>server<span class=\"token punctuation\">.</span>port<span class=\"token punctuation\">)</span>\n        price <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kraken<span class=\"token punctuation\">.</span>get_price<span class=\"token punctuation\">(</span><span class=\"token string\">'btcusd'</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">assert</span> price <span class=\"token operator\">==</span> Decimal<span class=\"token punctuation\">(</span><span class=\"token string\">'3602.20000'</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>This starts a test server on localhost that sends back a JSON hello message when\nqueried for <code class=\"language-text\">/hello</code>. This is all nice and well, but it falls short in several ways:</p>\n<ul>\n<li>Controlling the server throughout the test case is <b>not easy</b>. This was a simple example,\nbut how about an endpoint with a more complex behavior\n(say, a <a href=\"https://en.wikipedia.org/wiki/Representational_state_transfer\" class=\"external\" rel=\"external noopener noreferrer\">REST</a> API)?</li>\n<li>It is <b>cumbersome</b>. This must be copy-pasted in each and every test case. It cannot\nbe made a fixture since the handler is test-case specific.</li>\n<li>We <b>cannot control timing</b> easily. Say we need to check the behavior of client code\n<em>while it is waiting</em> for the response.</li>\n<li>It makes test case hard to read, as code is <b>out of order</b>: we define how to respond\nto queries before they are made.</li>\n</ul>\n<p>Let's build a better infrastructure for our tests.</p>\n<h3 id=\"handling-requests-in-test-cases\">Handling requests in test cases</h3>\n<p>The idea is simple: the test case is an asynchronous task. The test server is an\nasynchronous task as well. We should be able to use inter-task communication tools\nto have the server <strong>delegate request processing</strong> back to the test server.</p>\n<div class=\"figure-wrapper\"><figure id=\"fig-3\"><img src=\"/files/test-sequence-5a8a1163f12215f161565c61fd7ea506.png\" alt=\"Client request/response sequence under testcase control\" srcset=\"/files/test-sequence-1.5x-168c460628cdb4180915f8fe1524ef62.png 1.5x, /files/test-sequence-2x-8a0fdd9fc67ad39a4242d664b752bc6f.png 2x\"><figcaption><div class=\"figcaption-wrapper\">Client request/response sequence under testcase control.</div></figcaption></figure></div>\n<p>Let's implement this workflow. We extend aiohttp's <a href=\"https://aiohttp.readthedocs.io/en/stable/testing.html#aiohttp.test_utils.RawTestServer\" class=\"external\" rel=\"external noopener noreferrer\"><code class=\"language-text\">RawTestServer</code></a>\nand provide our own request handler. This is the main idea: instead of\nhandling the request, the handler pushes it to a queue and waits\nfor the test case to provide the response.</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">class</span> <span class=\"token class-name\">CaseControlledTestServer</span><span class=\"token punctuation\">(</span>aiohttp<span class=\"token punctuation\">.</span>test_utils<span class=\"token punctuation\">.</span>RawTestServer<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">def</span> <span class=\"token function\">__init__</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">,</span> <span class=\"token operator\">**</span>kwargs<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        <span class=\"token builtin\">super</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>__init__<span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">.</span>_handle_request<span class=\"token punctuation\">,</span> <span class=\"token operator\">**</span>kwargs<span class=\"token punctuation\">)</span>\n        self<span class=\"token punctuation\">.</span>_requests <span class=\"token operator\">=</span> asyncio<span class=\"token punctuation\">.</span>Queue<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        self<span class=\"token punctuation\">.</span>_responses <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span>                <span class=\"token comment\"># {id(request): Future}</span>\n\n    <span class=\"token keyword\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">close</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        <span class=\"token triple-quoted-string string\">''' cancel all pending requests before closing '''</span>\n        <span class=\"token keyword\">for</span> future <span class=\"token keyword\">in</span> self<span class=\"token punctuation\">.</span>_responses<span class=\"token punctuation\">.</span>values<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n            future<span class=\"token punctuation\">.</span>cancel<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">await</span> <span class=\"token builtin\">super</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>close<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\n    <span class=\"token keyword\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">_handle_request</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">,</span> request<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        <span class=\"token triple-quoted-string string\">''' push request to test case and wait until it provides a response '''</span>\n        self<span class=\"token punctuation\">.</span>_responses<span class=\"token punctuation\">[</span><span class=\"token builtin\">id</span><span class=\"token punctuation\">(</span>request<span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> response <span class=\"token operator\">=</span> asyncio<span class=\"token punctuation\">.</span>Future<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        self<span class=\"token punctuation\">.</span>_requests<span class=\"token punctuation\">.</span>put_nowait<span class=\"token punctuation\">(</span>request<span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">try</span><span class=\"token punctuation\">:</span>\n            <span class=\"token comment\"># wait until test case provides a response</span>\n            <span class=\"token keyword\">return</span> <span class=\"token keyword\">await</span> response\n        <span class=\"token keyword\">finally</span><span class=\"token punctuation\">:</span>\n            <span class=\"token keyword\">del</span> self<span class=\"token punctuation\">.</span>_responses<span class=\"token punctuation\">[</span><span class=\"token builtin\">id</span><span class=\"token punctuation\">(</span>request<span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span>\n\n    <span class=\"token keyword\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">receive_request</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        <span class=\"token triple-quoted-string string\">''' wait until test server receives a request '''</span>\n        <span class=\"token keyword\">return</span> <span class=\"token keyword\">await</span> self<span class=\"token punctuation\">.</span>_requests<span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\n    <span class=\"token keyword\">def</span> <span class=\"token function\">send_response</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">,</span> request<span class=\"token punctuation\">,</span> <span class=\"token operator\">*</span>args<span class=\"token punctuation\">,</span> <span class=\"token operator\">**</span>kwargs<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        <span class=\"token triple-quoted-string string\">''' send web response from test case to client code '''</span>\n        response <span class=\"token operator\">=</span> aiohttp<span class=\"token punctuation\">.</span>web<span class=\"token punctuation\">.</span>Response<span class=\"token punctuation\">(</span><span class=\"token operator\">*</span>args<span class=\"token punctuation\">,</span> <span class=\"token operator\">**</span>kwargs<span class=\"token punctuation\">)</span>\n        self<span class=\"token punctuation\">.</span>_responses<span class=\"token punctuation\">[</span><span class=\"token builtin\">id</span><span class=\"token punctuation\">(</span>request<span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span>set_result<span class=\"token punctuation\">(</span>response<span class=\"token punctuation\">)</span></code></pre></div>\n<p>Two things deserve an explanation:</p>\n<ul>\n<li>We represent the yet-to-be response using a <a href=\"https://docs.python.org/3/library/asyncio-future.html#asyncio.Future\" class=\"external\" rel=\"external noopener noreferrer\"><code class=\"language-text\">Future</code></a>. The request handler\nthen awaits the <code class=\"language-text\">Future</code>, which puts it to sleep until\n<a href=\"https://docs.python.org/3/library/asyncio-future.html#asyncio.Future.set_result\" class=\"external\" rel=\"external noopener noreferrer\"><code class=\"language-text\">Future.set_result()</code></a>\nis called. When this happens, the request handler awakens, and <code class=\"language-text\">await response</code>\nevaluates to the value that was passed to <code class=\"language-text\">Future.set_result()</code>. That is, the\n<a href=\"https://aiohttp.readthedocs.io/en/stable/web_reference.html#aiohttp.web.Response\" class=\"external\" rel=\"external noopener noreferrer\"><code class=\"language-text\">Response</code></a>, which it can return to <code class=\"language-text\">aiohttp</code> for sending on the network.</li>\n<li>For proper cleanup, we override <code class=\"language-text\">close()</code> and cancel all outstanding futures. This\nwill awaken waiting handlers immediately, causing them to raise a <code class=\"language-text\">CancelledError</code>\nexception. This ensures we don't leave blocked tasks behind.</li>\n</ul>\n<p>Using our <code class=\"language-text\">CaseControlledTestServer</code>, we can rewrite the previous example like this:</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token decorator annotation punctuation\">@pytest<span class=\"token punctuation\">.</span>mark<span class=\"token punctuation\">.</span>asyncio</span>\n<span class=\"token keyword\">def</span> <span class=\"token function\">test_example</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">async</span> <span class=\"token keyword\">with</span> CaseControlledTestServer<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">as</span> server<span class=\"token punctuation\">:</span>\n        kraken <span class=\"token operator\">=</span> Kraken<span class=\"token punctuation\">(</span>server<span class=\"token operator\">=</span><span class=\"token string\">'localhost'</span><span class=\"token punctuation\">,</span> port<span class=\"token operator\">=</span>server<span class=\"token punctuation\">.</span>port<span class=\"token punctuation\">)</span>\n\n        task <span class=\"token operator\">=</span> asyncio<span class=\"token punctuation\">.</span>create_task<span class=\"token punctuation\">(</span>kraken<span class=\"token punctuation\">.</span>get_price<span class=\"token punctuation\">(</span><span class=\"token string\">'btcusd'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n        request <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> server<span class=\"token punctuation\">.</span>receive_request<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">assert</span> request<span class=\"token punctuation\">.</span>path_qs <span class=\"token operator\">==</span> <span class=\"token string\">'/0/public/Ticker?pair=XXBTZUSD'</span>\n\n        server<span class=\"token punctuation\">.</span>send_response<span class=\"token punctuation\">(</span>request<span class=\"token punctuation\">,</span> text<span class=\"token operator\">=</span>SAMPLE_RESPONSE_FROM_KRAKEN<span class=\"token punctuation\">)</span>\n        price <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> task\n        <span class=\"token keyword\">assert</span> price <span class=\"token operator\">==</span> Decimal<span class=\"token punctuation\">(</span><span class=\"token string\">'3602.20000'</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>Much better already. Notice how linear the test case has become? The test\ncase walks through the steps of the asynchronous workflow, and <strong>gets access\nto the request object</strong>, which allows checking it, eg: veryfing the query string,\nor ensuring some header is present.</p>\n<p>Let's now fix the uri redirection we have ignored so far.</p>\n<h3 id=\"rerouting-network-accesses\">Rerouting network accesses</h3>\n<p>Up to this point, we have been manually providing server address and port to\nour client code. This scheme is inherently wrong though, as it <strong>breaks encapsulation</strong>:\nas it is, every single part of our project that needs to build a <code class=\"language-text\">Kraken</code> instance\nmust know the official server address and port.</p>\n<p><b>Let's fix this.</b></p>\n<p>Global state in <code class=\"language-text\">aiohttp</code> is cleanly wrapped in a <a href=\"https://aiohttp.readthedocs.io/en/stable/client_reference.html#aiohttp.ClientSession\" class=\"external\" rel=\"external noopener noreferrer\"><code class=\"language-text\">ClientSession</code></a>\nobject. That objects holds connection pools, settings, a cookie database, etc.\nThen, all classes that use <code class=\"language-text\">aiohttp</code> accept a <code class=\"language-text\">ClientSession</code> object at\nconstruction (as a dependency injection). This makes it easy to share resources\namong components or isolate them depending on desired behavior.</p>\n<p>The interesting part is <code class=\"language-text\">ClientSession</code> does not know how to access the network.\nThis is all abstracted out in a <em>connector</em>, which is injected into the session at\ncreation: <em>“here, anytime you need network access, use this connector”</em>.</p>\n<p>We are thus interested in altering the TCP connector, an instance of class\n<a href=\"https://aiohttp.readthedocs.io/en/stable/client_reference.html#aiohttp.TCPConnector\" class=\"external\" rel=\"external noopener noreferrer\"><code class=\"language-text\">aiohttp.TCPConnector</code></a>. Digging deeper, we can see that in turn,\nthat connector delegates address resolution to a <em>resolver</em> object.</p>\n<p>This is our target: we will provide a <strong>custom resolver</strong> that resolves tested addresses\nto local test servers and purposefully fail all other addresses, ensuring no\nconnection can be made to other servers.</p>\n<p>Let's write the resolver first:</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">class</span> <span class=\"token class-name\">FakeResolver</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">def</span> <span class=\"token function\">__init__</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        self<span class=\"token punctuation\">.</span>_servers <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">def</span> <span class=\"token function\">add</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">,</span> host<span class=\"token punctuation\">,</span> port<span class=\"token punctuation\">,</span> target_port<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        self<span class=\"token punctuation\">.</span>_servers<span class=\"token punctuation\">[</span>host<span class=\"token punctuation\">,</span> port<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> target_port\n\n    <span class=\"token keyword\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">resolve</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">,</span> host<span class=\"token punctuation\">,</span> port<span class=\"token operator\">=</span><span class=\"token number\">0</span><span class=\"token punctuation\">,</span> family<span class=\"token operator\">=</span>socket<span class=\"token punctuation\">.</span>AF_INET<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">try</span><span class=\"token punctuation\">:</span>\n            fake_port <span class=\"token operator\">=</span> self<span class=\"token punctuation\">.</span>_servers<span class=\"token punctuation\">[</span>host<span class=\"token punctuation\">,</span> port<span class=\"token punctuation\">]</span>\n        <span class=\"token keyword\">except</span> KeyError<span class=\"token punctuation\">:</span>\n            <span class=\"token keyword\">raise</span> OSError<span class=\"token punctuation\">(</span><span class=\"token string\">'No test server known for %s'</span> <span class=\"token operator\">%</span> host<span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">return</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">{</span>\n            <span class=\"token string\">'hostname'</span><span class=\"token punctuation\">:</span> host<span class=\"token punctuation\">,</span>\n            <span class=\"token string\">'host'</span><span class=\"token punctuation\">:</span> <span class=\"token string\">'127.0.0.1'</span><span class=\"token punctuation\">,</span>\n            <span class=\"token string\">'port'</span><span class=\"token punctuation\">:</span> fake_port<span class=\"token punctuation\">,</span>\n            <span class=\"token string\">'family'</span><span class=\"token punctuation\">:</span> socket<span class=\"token punctuation\">.</span>AF_INET<span class=\"token punctuation\">,</span>\n            <span class=\"token string\">'proto'</span><span class=\"token punctuation\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\n            <span class=\"token string\">'flags'</span><span class=\"token punctuation\">:</span> socket<span class=\"token punctuation\">.</span>AI_NUMERICHOST<span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">]</span></code></pre></div>\n<p>Straightforward: we record added test servers, and <code class=\"language-text\">resolve</code> raises an <code class=\"language-text\">OSError</code> if\nrequested address does not match a known test server.</p>\n<p>Now we want to inject our resolver into a connector and make it conveniently\navailable within a test-scoped <code class=\"language-text\">ClientSession</code> instance. A perfect use case for building\na fixture:</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\">_RedirectContext <span class=\"token operator\">=</span> namedtuple<span class=\"token punctuation\">(</span><span class=\"token string\">'RedirectContext'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'add_server session'</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token decorator annotation punctuation\">@pytest<span class=\"token punctuation\">.</span>fixture</span>\n<span class=\"token keyword\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">aiohttp_redirector</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    resolver <span class=\"token operator\">=</span> FakeResolver<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    connector <span class=\"token operator\">=</span> aiohttp<span class=\"token punctuation\">.</span>TCPConnector<span class=\"token punctuation\">(</span>resolver<span class=\"token operator\">=</span>resolver<span class=\"token punctuation\">,</span> use_dns_cache<span class=\"token operator\">=</span><span class=\"token boolean\">False</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">async</span> <span class=\"token keyword\">with</span> aiohttp<span class=\"token punctuation\">.</span>ClientSession<span class=\"token punctuation\">(</span>connector<span class=\"token operator\">=</span>connector<span class=\"token punctuation\">)</span> <span class=\"token keyword\">as</span> session<span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">yield</span> _RedirectContext<span class=\"token punctuation\">(</span>add_server<span class=\"token operator\">=</span>resolver<span class=\"token punctuation\">.</span>add<span class=\"token punctuation\">,</span> session<span class=\"token operator\">=</span>session<span class=\"token punctuation\">)</span></code></pre></div>\n<p>We can now use our fixture in our testcase example:</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token decorator annotation punctuation\">@pytest<span class=\"token punctuation\">.</span>mark<span class=\"token punctuation\">.</span>asyncio</span>\n<span class=\"token keyword\">def</span> <span class=\"token function\">test_example</span><span class=\"token punctuation\">(</span>aiohttp_redirector<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">async</span> <span class=\"token keyword\">with</span> CaseControlledTestServer<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">as</span> server<span class=\"token punctuation\">:</span>\n        aiohttp_redirector<span class=\"token punctuation\">.</span>add_server<span class=\"token punctuation\">(</span><span class=\"token string\">'api.kraken.com'</span><span class=\"token punctuation\">,</span> <span class=\"token number\">80</span><span class=\"token punctuation\">,</span> server<span class=\"token punctuation\">.</span>port<span class=\"token punctuation\">)</span>\n        kraken <span class=\"token operator\">=</span> Kraken<span class=\"token punctuation\">(</span>session<span class=\"token operator\">=</span>aiohttp_redirector<span class=\"token punctuation\">.</span>session<span class=\"token punctuation\">)</span>\n        <span class=\"token comment\">#</span>\n        <span class=\"token comment\"># --- rest is unchanged ---</span>\n        <span class=\"token comment\">#</span></code></pre></div>\n<p>Our <code class=\"language-text\">Kraken</code> class holds the knowledge of what it should connect to, and our\ntests uses a custom resolver to direct those connections to the test server.\nWe no longer break encapsulation.</p>\n<h2 id=\"recap\">Recap</h2>\n<p>In this first part, we have created our testing infrastructure.</p>\n<ul>\n<li>Our tests are now <strong>isolated</strong>, we run them against a local, test-case controlled server.</li>\n<li>Our tests are as <strong>faithful</strong> as possible. Perhaps integration tests would setup an\nactual web server along with our client code, but for unit tests it is as close to real\nconditions we can go.</li>\n<li>Our tests are <em>more</em> <strong>comprehensive</strong>. Having actual TCP connections happening, we\ncan test all error conditions that come with them. We still lack SSL layer support though.</li>\n<li>Our tests are <strong>unintrusive</strong>. As long as the client code follows the best practices and\nuses dependency injection to get a <code class=\"language-text\">ClientSession</code> instance, we can test it with no change.</li>\n<li>We left SSL aside for now.</li>\n</ul>\n<p>In the <a href=\"testing-aiohttp-client-part-2\" class=\"internal\">next post</a>, we will use our testing infrastructure\nto test and develop our Kraken feed.</p>","fields":{"isPage":false,"slug":"/2018/testing-aiohttp-client"},"frontmatter":{"title":"Unit Testing aiohttp Clients","classname":null,"date":"2018-11-25T00:00:00.000Z","formattedDate":"November 25, 2018","isoDate":"2018-11-25T00:00:00+00:00"},"headings":[{"value":"Objectives","depth":1},{"value":"Fetching bitcoin price","depth":2},{"value":"Testing goals","depth":2},{"value":"Test support tools","depth":1},{"value":"Checking out included batteries","depth":2},{"value":"Handling requests in test cases","depth":2},{"value":"Rerouting network accesses","depth":2},{"value":"Recap","depth":1}],"image":null,"series":{"name":"python","fullName":null,"fields":{"slug":"/python"}},"tags":[{"name":"code","slug":"/tag/code"},{"name":"testing","slug":"/tag/testing"}]}},"pageContext":{"series":"python","slug":"/2018/testing-aiohttp-client","previous":{"fields":{"slug":"/2018/cryptomate/other-strategy-ports"},"frontmatter":{"title":"Other Strategy Ports","series":"cryptomate"},"tags":[{"name":"architecture","slug":"/tag/architecture"},{"name":"ports","slug":"/tag/ports"}]},"next":{"fields":{"slug":"/2018/testing-aiohttp-client-part-2"},"frontmatter":{"title":"Unit Testing aiohttp Clients - part 2","series":"python"},"tags":[{"name":"code","slug":"/tag/code"},{"name":"testing","slug":"/tag/testing"}]},"seriesPrevious":{"fields":{"slug":"/2018/starting-python-project"},"frontmatter":{"title":"Starting a Python Project","series":"python"},"tags":[{"name":"code","slug":"/tag/code"}]},"seriesNext":{"fields":{"slug":"/2018/testing-aiohttp-client-part-2"},"frontmatter":{"title":"Unit Testing aiohttp Clients - part 2","series":"python"},"tags":[{"name":"code","slug":"/tag/code"},{"name":"testing","slug":"/tag/testing"}]}}},
    "staticQueryHashes": ["1733002695","4006707078"]}