{
    "componentChunkName": "component---src-templates-post-page-js",
    "path": "/2018/testing-aiohttp-client-part-2",
    "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":"b494795b-311b-5972-8bdd-ce704cd66f50","excerpt":"In the previous post we designed our testing infrastructure to work with aiohttp.\nWe will now put that testing infrastructure through a baptism by fire.","html":"<p>In the previous post we designed our testing infrastructure to work with aiohttp.\nWe will now put that testing infrastructure through a baptism by fire.</p>\n<p>--- excerpt ---</p>\n<p>In the <a href=\"testing-aiohttp-client\" class=\"internal\">previous post</a> we designed our testing infrastructure\nto work with <a href=\"https://aiohttp.readthedocs.io/\" class=\"external\" rel=\"external noopener noreferrer\"><code class=\"language-text\">aiohttp</code></a>. We will now put that testing infrastructure through\na baptism by fire.</p>\n<blockquote>\n<ol>\n<li><a href=\"testing-aiohttp-client\" class=\"internal\">Testing tools</a>.</li>\n<li>Actual implementation <em>(this post)</em>.</li>\n</ol>\n</blockquote>\n<p>If you remember, we were designing a simple class to fetch the current price of\nbitcoin from Kraken exchange. So far we only have the interface. Let's implement it\nand ensure its correctness.</p>\n<p>We will use a TDD approach, that is: write tests against the interface first, then\nimplement the code required to make tests pass.</p>\n<h2 id=\"some-test-driven-development\">Some Test-driven development</h2>\n<h3 id=\"writing-the-test-cases\">Writing the test cases</h3>\n<p>We have a single interface, with a single method. Not much to add. Here is a trimmed down\nversion of the main tests, you can see the full test suite in\n<a href=\"https://github.com/solid-abstractions/python/blob/master/testing-aiohttp-clients/tests/test_kraken.py\" class=\"external\" rel=\"external noopener noreferrer\"><code class=\"language-text\">test_kraken.py</code></a>.</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\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">test_get_price</span><span class=\"token punctuation\">(</span>aiohttp_redirector<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token triple-quoted-string string\">''' get_price returns price of latest transaction '''</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\n        task <span class=\"token operator\">=</span> asyncio<span class=\"token punctuation\">.</span>ensure_future<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>\n            text<span class=\"token operator\">=</span><span class=\"token string\">'{\"error\":[],\"result\":{\"XXBTZUSD\":{\"a\":[\"3652.20000\",'</span>\n                 <span class=\"token string\">'\"2\",\"2.000\"],\"b\":[\"3651.10000\",\"1\",\"1.000\"],'</span>\n                 <span class=\"token string\">'\"c\":[\"3652.20000\",\"1.00000000\"],\"o\":\"3727.50000\"}}}'</span><span class=\"token punctuation\">,</span>\n            content_type<span class=\"token operator\">=</span><span class=\"token string\">'application/json'</span>\n        <span class=\"token punctuation\">)</span>\n        result <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> task\n        <span class=\"token keyword\">assert</span> result <span class=\"token operator\">==</span> Decimal<span class=\"token punctuation\">(</span><span class=\"token string\">'3652.20000'</span><span class=\"token punctuation\">)</span>\n\n\n<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\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">test_get_price_connection_failure</span><span class=\"token punctuation\">(</span>aiohttp_redirector<span class=\"token punctuation\">,</span> unused_tcp_port<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token triple-quoted-string string\">''' failure to connect raises a QuotationError exception '''</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> unused_tcp_port<span class=\"token punctuation\">)</span>\n\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 keyword\">with</span> pytest<span class=\"token punctuation\">.</span>raises<span class=\"token punctuation\">(</span>QuotationError<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        <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\n\n<span class=\"token decorator annotation punctuation\">@pytest<span class=\"token punctuation\">.</span>mark<span class=\"token punctuation\">.</span>parametrize</span><span class=\"token punctuation\">(</span><span class=\"token string\">'payload'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">[</span>\n    pytest<span class=\"token punctuation\">.</span>param<span class=\"token punctuation\">(</span><span class=\"token string\">'foobar'</span><span class=\"token punctuation\">,</span> <span class=\"token builtin\">id</span><span class=\"token operator\">=</span><span class=\"token string\">'not json'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    pytest<span class=\"token punctuation\">.</span>param<span class=\"token punctuation\">(</span><span class=\"token string\">'{\"foo\":\"bar\"}'</span><span class=\"token punctuation\">,</span> <span class=\"token builtin\">id</span><span class=\"token operator\">=</span><span class=\"token string\">'not in kraken format'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    pytest<span class=\"token punctuation\">.</span>param<span class=\"token punctuation\">(</span><span class=\"token string\">'{\"error\":[], \"result\": {}}'</span><span class=\"token punctuation\">,</span> <span class=\"token builtin\">id</span><span class=\"token operator\">=</span><span class=\"token string\">'missing data'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    pytest<span class=\"token punctuation\">.</span>param<span class=\"token punctuation\">(</span>\n        <span class=\"token string\">'{\"error\":[], \"result\": {\"XXBTZUSD\":{}}}'</span><span class=\"token punctuation\">,</span>\n        <span class=\"token builtin\">id</span><span class=\"token operator\">=</span><span class=\"token string\">'missing price data'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    pytest<span class=\"token punctuation\">.</span>param<span class=\"token punctuation\">(</span>\n        <span class=\"token string\">'{\"error\":[], \"result\":{\"XXBTZUSD\":{\"a\":[\"3652.20000\",\"2\",\"2.000\"],'</span>\n        <span class=\"token string\">'\"b\":[\"3651.10000\",\"1\",\"1.000\"],\"c\":[\"123.456.789\",\"1.00000000\"],'</span>\n        <span class=\"token string\">'\"o\":\"3727.50000\"}}}'</span><span class=\"token punctuation\">,</span>\n        <span class=\"token builtin\">id</span><span class=\"token operator\">=</span><span class=\"token string\">'invalid price format'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span>\n<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\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">test_get_price_invalid_payload</span><span class=\"token punctuation\">(</span>aiohttp_redirector<span class=\"token punctuation\">,</span> payload<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token triple-quoted-string string\">''' invalid data from server raises a PayloadError exception '''</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\n        task <span class=\"token operator\">=</span> asyncio<span class=\"token punctuation\">.</span>ensure_future<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>timeout<span class=\"token operator\">=</span>TIMEOUT<span class=\"token punctuation\">)</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>payload<span class=\"token punctuation\">,</span>\n                             content_type<span class=\"token operator\">=</span><span class=\"token string\">'application/json'</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">with</span> pytest<span class=\"token punctuation\">.</span>raises<span class=\"token punctuation\">(</span>PayloadError<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n            <span class=\"token keyword\">await</span> task</code></pre></div>\n<p>Starting <code class=\"language-text\">pytest</code>, we check that our tests work correctly.\nThat is, they all run, and all return a failure.\nGreat, let's move to implementation.</p>\n<h3 id=\"implementation\">Implementation</h3>\n<p>Finally, we can now implement our <code class=\"language-text\">Kraken</code> class:</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\">Kraken</span><span class=\"token punctuation\">:</span>\n    ROOT_URL <span class=\"token operator\">=</span> <span class=\"token string\">'http://api.kraken.com/0/'</span>\n    TICKER_ENDPOINT <span class=\"token operator\">=</span> <span class=\"token string\">'public/Ticker?pair={pair}'</span>\n    PAIRS <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token string\">'btcusd'</span><span class=\"token punctuation\">:</span> <span class=\"token string\">'XXBTZUSD'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'ethusd'</span><span class=\"token punctuation\">:</span> <span class=\"token string\">'XETHZUSD'</span><span class=\"token punctuation\">,</span>\n             <span class=\"token string\">'ltcusd'</span><span class=\"token punctuation\">:</span> <span class=\"token string\">'XLTCZUSD'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'xrpusd'</span><span class=\"token punctuation\">:</span> <span class=\"token string\">'XXRPZUSD'</span><span class=\"token punctuation\">}</span>\n\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><span class=\"token punctuation\">,</span> session<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        self<span class=\"token punctuation\">.</span>_session <span class=\"token operator\">=</span> session\n\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\">''' Return latest trade price for given pair '''</span>\n        pair <span class=\"token operator\">=</span> self<span class=\"token punctuation\">.</span>PAIRS<span class=\"token punctuation\">[</span>pair<span class=\"token punctuation\">]</span>\n        data <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> self<span class=\"token punctuation\">.</span>_fetch<span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">.</span>TICKER_ENDPOINT<span class=\"token punctuation\">.</span><span class=\"token builtin\">format</span><span class=\"token punctuation\">(</span>pair<span class=\"token operator\">=</span>pair<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">return</span> decimal<span class=\"token punctuation\">.</span>Decimal<span class=\"token punctuation\">(</span>data<span class=\"token punctuation\">[</span>pair<span class=\"token punctuation\">]</span><span class=\"token punctuation\">[</span><span class=\"token string\">'c'</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">[</span><span class=\"token number\">0</span><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\">_fetch</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">,</span> endpoint<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        <span class=\"token triple-quoted-string string\">''' Fetch data from endpoint and parse result '''</span>\n        <span class=\"token keyword\">async</span> <span class=\"token keyword\">with</span> self<span class=\"token punctuation\">.</span>_session<span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">.</span>ROOT_URL <span class=\"token operator\">+</span> endpoint<span class=\"token punctuation\">)</span> <span class=\"token keyword\">as</span> response<span class=\"token punctuation\">:</span>\n            data <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> response<span class=\"token punctuation\">.</span>json<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        error <span class=\"token operator\">=</span> data<span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span><span class=\"token string\">'error'</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">if</span> error<span class=\"token punctuation\">:</span>\n            <span class=\"token keyword\">raise</span> QuotationError<span class=\"token punctuation\">(</span>error<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">return</span> data<span class=\"token punctuation\">[</span><span class=\"token string\">'result'</span><span class=\"token punctuation\">]</span></code></pre></div>\n<p>Some error handling was left aside for clarity. You can check the full implementation in\n<a href=\"https://github.com/solid-abstractions/python/blob/master/testing-aiohttp-clients/kraken.py\" class=\"external\" rel=\"external noopener noreferrer\"><code class=\"language-text\">kraken.py</code></a>.\nAlso I worked around the lack of SSL support by changing the URL from <code class=\"language-text\">https</code> to <code class=\"language-text\">http</code> for now.</p>\n<h3 id=\"checking-progress\">Checking progress</h3>\n<p>Before adding support for SSL, let's see how we fare:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">(env) spectras$ pytest -v\n============================= test session starts ==============================\ncollecting ... collected 8 items\n\ntests/test_kraken.py::test_get_price_control PASSED                      [ 11%]\ntests/test_kraken.py::test_get_price_invalid_pair PASSED                 [ 22%]\ntests/test_kraken.py::test_get_price_connection_failure PASSED           [ 33%]\ntests/test_kraken.py::test_get_price_invalid_certificate SKIPPED         [ 44%]\ntests/test_kraken.py::test_get_price_invalid_payload[not json] PASSED    [ 55%]\ntests/test_kraken.py::test_get_price_invalid_payload[not in kraken format] PASSED [ 66%]\ntests/test_kraken.py::test_get_price_invalid_payload[missing data] PASSED [ 77%]\ntests/test_kraken.py::test_get_price_invalid_payload[missing price data] PASSED [ 88%]\ntests/test_kraken.py::test_get_price_invalid_payload[invalid price] PASSED [100%]\n\n====================== 8 passed, 1 skipped in 0.12 seconds =====================</code></pre></div>\n<p><b>Great! Let's fix SSL now.</b></p>\n<h2 id=\"self-signed-certificate\">Self-signed certificate</h2>\n<p>All that is left from our initial objectives is SSL support. It is not as hard as\nit sounds. We have to create a self-signed certificate for use during the\ntests. Perfect use for a fixture.</p>\n<p>As it is not the main concern of this article, I will spare you the code. You can\nfind it on the github repository, in\n<a href=\"https://github.com/solid-abstractions/python/blob/master/testing-aiohttp-clients/tests/certificate.py\" class=\"external\" rel=\"external noopener noreferrer\"><code class=\"language-text\">certificate.py</code></a>.\nOnce loaded, that fixture creates a self-signed server certificate for the whole\ntest session, valid only for local system and expiring after 24 hours.</p>\n<p>That fixture is available to tests as <code class=\"language-text\">ssl_certificate</code>.</p>\n<h3 id=\"client-side\">Client side</h3>\n<p>We must ensure our test clients accepts our certificate. This is done by loading\nit into the <a href=\"https://aiohttp.readthedocs.io/en/stable/client_reference.html#aiohttp.TCPConnector\" class=\"external\" rel=\"external noopener noreferrer\"><code class=\"language-text\">TCPConnector</code></a> used by <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>.</p>\n<p>To that end, we go back to the <code class=\"language-text\">aiohttp_redirector</code> we built earlier, and modify it\nso it injects the <code class=\"language-text\">ssl_certificate</code> fixture into the session:</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token decorator annotation punctuation\">@pytest<span class=\"token punctuation\">.</span>fixture</span>\n<span class=\"gatsby-highlight-code-line\"><span class=\"token keyword\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">aiohttp_redirector</span><span class=\"token punctuation\">(</span>ssl_certificate<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span></span>    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>\n        resolver<span class=\"token operator\">=</span>resolver<span class=\"token punctuation\">,</span>\n<span class=\"gatsby-highlight-code-line\">        ssl<span class=\"token operator\">=</span>ssl_certificate<span class=\"token punctuation\">.</span>client_context<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span></span>        use_dns_cache<span class=\"token operator\">=</span><span class=\"token boolean\">False</span><span class=\"token punctuation\">,</span>\n    <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>This is it, SSL certificate verfication is now <strong>enabled</strong>, and trusts only <strong>one certificate</strong>: the\ntemporary self-signed certificate.</p>\n<h3 id=\"server-side\">Server side</h3>\n<p>The <code class=\"language-text\">RawTestServer</code> we extend from <code class=\"language-text\">aiohttp</code> accepts an <a href=\"https://docs.python.org/3/library/ssl.html#ssl-contexts\" class=\"external\" rel=\"external noopener noreferrer\">SSL context</a>,\nbut not in a convenient way: it requires dropping the <code class=\"language-text\">async with</code> that provides\nlifecycle management, and replacing it with a manual <code class=\"language-text\">try…except</code> block. It's cumbersome\nand easy to get wrong.</p>\n<p><strong>Let's pass it around ourselves instead.</strong> We update the constructor to accept the ssl context:</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"gatsby-highlight-code-line\">    <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><span class=\"token punctuation\">,</span> ssl<span class=\"token operator\">=</span><span class=\"token boolean\">None</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">**</span>kwargs<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span></span>        <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<span class=\"gatsby-highlight-code-line\">        self<span class=\"token punctuation\">.</span>_ssl <span class=\"token operator\">=</span> ssl</span>        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></code></pre></div>\n<p>Then we override <code class=\"language-text\">start_server</code> to use stored ssl context automatically:</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\">    <span class=\"token keyword\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">start_server</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=\"gatsby-highlight-code-line\">        kwargs<span class=\"token punctuation\">.</span>setdefault<span class=\"token punctuation\">(</span><span class=\"token string\">'ssl'</span><span class=\"token punctuation\">,</span> self<span class=\"token punctuation\">.</span>_ssl<span class=\"token punctuation\">)</span></span>        <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>start_server<span class=\"token punctuation\">(</span><span class=\"token operator\">**</span>kwargs<span class=\"token punctuation\">)</span></code></pre></div>\n<h3 id=\"updating-test-cases\">Updating test cases</h3>\n<p>We are done adding SSL support to our testing infrastructure. <b>Let's use it for our\nKraken tests!</b> Firstly, <strong>put <code class=\"language-text\">https</code> back</strong> everywhere, and update our tests accordingly.</p>\n<p>That's 3 lines to update in every test:</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" 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=\"gatsby-highlight-code-line\"><span class=\"token keyword\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">test_get_price</span><span class=\"token punctuation\">(</span>aiohttp_redirector<span class=\"token punctuation\">,</span> ssl_certificate<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span></span>    <span class=\"token triple-quoted-string string\">''' get_price returns price of latest transaction '''</span>\n<span class=\"gatsby-highlight-code-line\">    <span class=\"token keyword\">async</span> <span class=\"token keyword\">with</span> CaseTestServer<span class=\"token punctuation\">(</span>ssl<span class=\"token operator\">=</span>ssl_certificate<span class=\"token punctuation\">.</span>server_context<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">as</span> server<span class=\"token punctuation\">:</span></span><span class=\"gatsby-highlight-code-line\">        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\">443</span><span class=\"token punctuation\">,</span> server<span class=\"token punctuation\">.</span>port<span class=\"token punctuation\">)</span></span>        <span class=\"token comment\">#</span>\n        <span class=\"token comment\"># --- rest of test case is unchanged ---</span>\n        <span class=\"token comment\">#</span></code></pre></div>\n<p>We start <code class=\"language-text\">pytest</code> again and check passing test cases still pass. But now, they pass <strong>in SSL!</strong></p>\n<h3 id=\"ssl-certificate-test-case\">SSL Certificate test case</h3>\n<p>We can now write our final test case: verify that our client does not talk to the server\nif it has an invalid certificate:</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" 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\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">test_get_price_invalid_certificate</span><span class=\"token punctuation\">(</span>aiohttp_redirector<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n\n<span class=\"gatsby-highlight-code-line\">    <span class=\"token keyword\">with</span> TemporaryCertificate<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">as</span> wrong_cert<span class=\"token punctuation\">:</span></span>        <span class=\"token keyword\">async</span> <span class=\"token keyword\">with</span> CaseTestServer<span class=\"token punctuation\">(</span>ssl<span class=\"token operator\">=</span>wrong_cert<span class=\"token punctuation\">.</span>server_context<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">as</span> server<span class=\"token punctuation\">:</span>\n            http_redirect<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\">443</span><span class=\"token punctuation\">,</span> server<span class=\"token punctuation\">.</span>port<span class=\"token punctuation\">)</span>\n\n            kraken <span class=\"token operator\">=</span> Kraken<span class=\"token punctuation\">(</span>session<span class=\"token operator\">=</span>http_redirect<span class=\"token punctuation\">.</span>session<span class=\"token punctuation\">)</span>\n            <span class=\"token keyword\">with</span> pytest<span class=\"token punctuation\">.</span>raises<span class=\"token punctuation\">(</span>QuotationError<span class=\"token punctuation\">)</span> <span class=\"token keyword\">as</span> exc_info<span class=\"token punctuation\">:</span>\n                <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\n<span class=\"gatsby-highlight-code-line\">    <span class=\"token keyword\">assert</span> <span class=\"token builtin\">isinstance</span><span class=\"token punctuation\">(</span>exc_info<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">.</span>__cause__<span class=\"token punctuation\">,</span> aiohttp<span class=\"token punctuation\">.</span>ClientConnectorSSLError<span class=\"token punctuation\">)</span></span></code></pre></div>\n<p>Notice how we start our server with a new, <strong>different</strong> temporary certificate, instead of using\nthe trusted one from the fixture. We then ensure the client raises a <code class=\"language-text\">QuotationError</code> with\nthe invalid certificate as a cause.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">tests/test_kraken.py::test_get_price_invalid_certificate PASSED          [ 44%]</code></pre></div>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>Our journey ends here for now. The final code is available on\n<a href=\"https://github.com/solid-abstractions/python/tree/master/testing-aiohttp-clients\" class=\"external\" rel=\"external noopener noreferrer\">github</a>.\nDuly tested, fully asynchronous simple price fetcher for Kraken exchange.</p>\n<p>We did not explore all the possibilities of the test server we built, but manually\ncontrolling requests and responses from the test case allows <strong>controlling the timing\nand ordering</strong> of parallel requests, a crucial point for testing more complex clients.</p>\n<p>Speaking of timing, the most observant should have noticed we did not handle one\nspecific class of errors: timeouts. They are correct! But this is not specific to\naiohttp clients, and will deserve an article of its own.</p>\n<p>Did you enjoy this post? Did you design a better way to test asynchronous http clients?\n<a href=\"/about\" class=\"internal\">Let me know!</a>.</p>","fields":{"isPage":false,"slug":"/2018/testing-aiohttp-client-part-2"},"frontmatter":{"title":"Unit Testing aiohttp Clients - part 2","classname":null,"date":"2018-11-30T00:00:00.000Z","formattedDate":"November 30, 2018","isoDate":"2018-11-30T00:00:00+00:00"},"headings":[{"value":"Some Test-driven development","depth":1},{"value":"Writing the test cases","depth":2},{"value":"Implementation","depth":2},{"value":"Checking progress","depth":2},{"value":"Self-signed certificate","depth":1},{"value":"Client side","depth":2},{"value":"Server side","depth":2},{"value":"Updating test cases","depth":2},{"value":"SSL Certificate test case","depth":2},{"value":"Conclusion","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-part-2","previous":{"fields":{"slug":"/2018/testing-aiohttp-client"},"frontmatter":{"title":"Unit Testing aiohttp Clients","series":"python"},"tags":[{"name":"code","slug":"/tag/code"},{"name":"testing","slug":"/tag/testing"}]},"next":{"fields":{"slug":"/2019/error-handling-introduction"},"frontmatter":{"title":"Error Handling part 1: Introduction","series":"software"},"tags":[{"name":"code","slug":"/tag/code"}]},"seriesPrevious":{"fields":{"slug":"/2018/testing-aiohttp-client"},"frontmatter":{"title":"Unit Testing aiohttp Clients","series":"python"},"tags":[{"name":"code","slug":"/tag/code"},{"name":"testing","slug":"/tag/testing"}]},"seriesNext":null}},
    "staticQueryHashes": ["1733002695","4006707078"]}