<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Websockets on Haseeb Majid</title>
    <link>https://haseebmajid.dev/tags/websockets/</link>
    <description>Recent content in Websockets on Haseeb Majid</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Fri, 31 Dec 2021 00:00:00 +0000</lastBuildDate><atom:link href="https://haseebmajid.dev/tags/websockets/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Separate function handler modules when using Python Socketio</title>
      <link>https://haseebmajid.dev/posts/2021-12-31-separate-function-handler-modules-when-using-python-socketio/</link>
      <pubDate>Fri, 31 Dec 2021 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2021-12-31-separate-function-handler-modules-when-using-python-socketio/</guid>
      <description>&lt;p&gt;In this article I will show you how you can have separate modules for your Socketio event handlers.
Rather than keeping them all in the same file.&lt;/p&gt;
&lt;p&gt;Hopefully this should be a relatively short article, lets get into it&lt;/p&gt;
&lt;h2 id=&#34;main&#34;&gt;Main&lt;/h2&gt;
&lt;p&gt;In this example I will be using SocketIO alongside FastAPI, but you can easily change this code to be SocketIO
only. I also will be using a uvicorn to run the server.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article I will show you how you can have separate modules for your Socketio event handlers.
Rather than keeping them all in the same file.</p>
<p>Hopefully this should be a relatively short article, lets get into it</p>
<h2 id="main">Main</h2>
<p>In this example I will be using SocketIO alongside FastAPI, but you can easily change this code to be SocketIO
only. I also will be using a uvicorn to run the server.</p>
<p>For example</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fastapi_socketio</span> <span class="kn">import</span> <span class="n">SocketManager</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">application</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;banter-bus-core-api&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">socket_manager</span> <span class="o">=</span> <span class="n">SocketManager</span><span class="p">(</span><span class="n">app</span><span class="o">=</span><span class="n">application</span><span class="p">,</span> <span class="n">mount_location</span><span class="o">=</span><span class="s2">&#34;/&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Here is where we setup our FastAPI application and create a Socketio server as a sub-application and mount it.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">fastapi-socketio</summary>
  
  Here I am using the <code>fastapi-socketio</code> library to handle mounting the application into the Fastapi app.
But this again can be done without the library, see this <a href="https://github.com/tiangolo/fastapi/issues/129#issuecomment-547806432">Github issue</a> for an example.
</details>

<p>Anyhow we could simply do something like, this create a Socketio only server without FastAPI.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">socketio</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">sio</span> <span class="o">=</span> <span class="n">socketio</span><span class="o">.</span><span class="n">AsyncServer</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">application</span> <span class="o">=</span> <span class="n">socketio</span><span class="o">.</span><span class="n">ASGIApp</span><span class="p">(</span><span class="n">sio</span><span class="p">)</span>
</span></span></code></pre></div><h2 id="handler-module">Handler Module</h2>
<p>Next lets take a look at the module which will handle our various events it should listen to from the client.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">app.main</span> <span class="kn">import</span> <span class="n">socket_manager</span> <span class="k">as</span> <span class="n">sm</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@sm.on</span><span class="p">(</span><span class="s2">&#34;FOO&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">foo_event</span><span class="p">(</span><span class="n">sid</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sm</span><span class="o">.</span><span class="n">emit</span><span class="p">(</span><span class="s2">&#34;BAR&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;response&#34;</span><span class="p">:</span> <span class="s2">&#34;hello world!&#34;</span><span class="p">})</span>
</span></span></code></pre></div><p>As you can see this handler imports the socket manager object in the second example this would be the object called <code> sio</code>. Then decorates a function, this function then will be called everytime a client sends an <code>FOO</code> event
to our web server. In this example it returns a <code>BAR</code> event (emits it) with <code>hello world</code>.
What this function does specifically doesn&rsquo;t really matter.</p>
<h2 id="initpy"><strong>init</strong>.py</h2>
<p>Finally let&rsquo;s put all this in our <code>app/__init__.py</code> module:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">uvicorn</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">app.foo.foo_handlers</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">app.main</span> <span class="kn">import</span> <span class="n">application</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">application</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">uvicorn</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="s2">&#34;0.0.0.0&#34;</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">8080</span><span class="p">)</span>
</span></span></code></pre></div><p>This may look a bit confusing, essentially this is the module that uvicorn will call directly to start the server. When then import our function handlers <code>import app.foo.foo_handlers</code>, here you will need to import
all of your function handlers, even though they aren&rsquo;t used here. So this is so that your application knows
they exist.</p>
<p>Without this import your application will have no way to &ldquo;attach&rdquo; them to the app. Now everytime the <code>FOO</code>
event is emitted from a client, your server knows to send it that function.</p>
<p>Finally we create a simple dummy variable <code>app = application</code> where application is the FastAPI/ASGIApp we
created in the <code>main.py</code> module. You could leave this as application but usually when using uvicorn
, i.e. looking at examples it will use <code>app</code>. So hence I&rsquo;ve renamed it here.</p>
<p>The final block is not really needed:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">uvicorn</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="s2">&#34;0.0.0.0&#34;</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">8080</span><span class="p">)</span>
</span></span></code></pre></div><p>It more exists if this module is used as a main file and will start the uvicorn server for us. However typically I will start the uvicorn server myself, usually in my Docker images or launch.json (VSCode debugger) config etc.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">uvicorn app:app --host <span class="s2">&#34;0.0.0.0&#34;</span> --port <span class="m">8080</span>
</span></span></code></pre></div><h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/banter-bus/banter-bus-core-api">Example project using this pattern</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Testing a socketio Web App written in Python</title>
      <link>https://haseebmajid.dev/posts/2021-12-23-testing-a-socketio-web-app-written-in-python/</link>
      <pubDate>Thu, 23 Dec 2021 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2021-12-23-testing-a-socketio-web-app-written-in-python/</guid>
      <description>&lt;p&gt;In this article I will show you how you can test an async Socketio application in Python, where the ASGI server we are running is uvicorn.
I will be referring to these tests as integration tests, though depending on who you ask they could be called E2E tests, system tests, slow test etc.
What I am referring to is simply testing out the entire &amp;ldquo;flow&amp;rdquo; of a socketio event i.e. emitting an event from a client, then receiving it on the web service
and for my actual projects interacting with an actual database.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article I will show you how you can test an async Socketio application in Python, where the ASGI server we are running is uvicorn.
I will be referring to these tests as integration tests, though depending on who you ask they could be called E2E tests, system tests, slow test etc.
What I am referring to is simply testing out the entire &ldquo;flow&rdquo; of a socketio event i.e. emitting an event from a client, then receiving it on the web service
and for my actual projects interacting with an actual database.</p>
<p>We will be using <code>pytest</code> as our testing framework.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">ASGI</summary>
  
  ASGI (Asynchronous Server Gateway Interface) is a spiritual successor to WSGI, intended to provide a standard interface between async-capable Python web servers, frameworks, and applications. - <a href="https://asgi.readthedocs.io/en/latest/">https://asgi.readthedocs.io/en/latest/</a>
</details>

<h2 id="mainpy">main.py</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">socketio</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">uvicorn</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pydantic</span> <span class="kn">import</span> <span class="n">BaseModel</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">startup</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Starting Application&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">sio</span> <span class="o">=</span> <span class="n">socketio</span><span class="o">.</span><span class="n">AsyncServer</span><span class="p">(</span><span class="n">async_mode</span><span class="o">=</span><span class="s2">&#34;asgi&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">socketio</span><span class="o">.</span><span class="n">ASGIApp</span><span class="p">(</span><span class="n">sio</span><span class="p">,</span> <span class="n">on_startup</span><span class="o">=</span><span class="n">startup</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">FooEvent</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@sio.on</span><span class="p">(</span><span class="s2">&#34;FOO&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">foo_event</span><span class="p">(</span><span class="n">sid</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">FooEvent</span><span class="p">(</span><span class="o">**</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sio</span><span class="o">.</span><span class="n">emit</span><span class="p">(</span><span class="s2">&#34;BAR&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;foo&#34;</span><span class="p">:</span> <span class="n">data</span><span class="o">.</span><span class="n">name</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">uvicorn</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="s2">&#34;0.0.0.0&#34;</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">8080</span><span class="p">)</span>
</span></span></code></pre></div><p>Let&rsquo;s take a look at our socketio app. Which is a very simple web app, that listens to one event <code>FOO</code> and
responds with a <code>BAR</code> event. It is just this single file.</p>
<h2 id="conftestpy">conftest.py</h2>
<p>The <code>conftest.py</code> file is automatically run by pytest and allows our test modules to access fixtures defined
in this file. One of the best features of Pytest is fixtures. Fixture are functions that have re-usable bits of code we
can run in our tests, such as static data used by tests.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">AsyncIterator</span><span class="p">,</span> <span class="n">Awaitable</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">socketio</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">uvicorn</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">app</span> <span class="kn">import</span> <span class="n">main</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">socketio</span> <span class="kn">import</span> <span class="n">ASGIApp</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">socketio.asyncio_client</span> <span class="kn">import</span> <span class="n">AsyncClient</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">PORT</span> <span class="o">=</span> <span class="mi">8000</span>
</span></span><span class="line"><span class="cl"><span class="n">LISTENING_IF</span> <span class="o">=</span> <span class="s2">&#34;127.0.0.1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">BASE_URL</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;http://</span><span class="si">{</span><span class="n">LISTENING_IF</span><span class="si">}</span><span class="s2">:</span><span class="si">{</span><span class="n">PORT</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">UvicornTestServer</span><span class="p">(</span><span class="n">uvicorn</span><span class="o">.</span><span class="n">Server</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="n">ASGIApp</span> <span class="o">=</span> <span class="n">main</span><span class="o">.</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">LISTENING_IF</span><span class="p">,</span> <span class="n">port</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">PORT</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Event</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Awaitable</span><span class="p">[</span><span class="n">Any</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">config</span><span class="o">=</span><span class="n">uvicorn</span><span class="o">.</span><span class="n">Config</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="n">port</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">startup</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Override uvicorn startup&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">startup</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">setup_event_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span><span class="o">.</span><span class="n">set</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">start_up</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Start up server asynchronously&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">serve</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span><span class="o">.</span><span class="n">wait</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">tear_down</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Shut down server asynchronously&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">should_exit</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">event_loop</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_event_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span> <span class="n">loop</span>
</span></span><span class="line"><span class="cl">    <span class="n">loop</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">startup_and_shutdown_server</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">server</span> <span class="o">=</span> <span class="n">UvicornTestServer</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">server</span><span class="o">.</span><span class="n">start_up</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">server</span><span class="o">.</span><span class="n">tear_down</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">client</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">AsyncIterator</span><span class="p">[</span><span class="n">AsyncClient</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="n">sio</span> <span class="o">=</span> <span class="n">socketio</span><span class="o">.</span><span class="n">AsyncClient</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sio</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">BASE_URL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span> <span class="n">sio</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sio</span><span class="o">.</span><span class="n">disconnect</span><span class="p">()</span>
</span></span></code></pre></div><h3 id="quick-aside-fastapi-testing">Quick Aside FastAPI Testing</h3>
<details
  class="notice danger"
  open="true"
>
    <summary class="notice-title">Uvicorn</summary>
  
  tl:dr: We need to start and stop the Uvicorn server within our tests.
</details>

<p>Now when testing say a FastAPI application, it has a builtin test client we can use. This means we don&rsquo;t actually have
to spin up a Uvicorn server to test our application. We can simply pretend to send requests to the FastAPI web service
and it will handle the routing behind the scenes.</p>
<p>We can do something like this, where <code>httpx</code> is a async HTTP client (think like the <code>requests</code> library).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">asgi_lifespan</span> <span class="kn">import</span> <span class="n">LifespanManager</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">httpx</span> <span class="kn">import</span> <span class="n">AsyncClient</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">app.main</span> <span class="kn">import</span> <span class="n">app</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">client</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">AsyncIterator</span><span class="p">[</span><span class="n">AsyncClient</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">LifespanManager</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">async</span> <span class="k">with</span> <span class="n">AsyncClient</span><span class="p">(</span><span class="n">app</span><span class="o">=</span><span class="n">app</span><span class="p">,</span> <span class="n">base_url</span><span class="o">=</span><span class="s2">&#34;http://localhost&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">client</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">yield</span> <span class="n">client</span>
</span></span></code></pre></div><p>Then we can use it like so in our tests:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">status</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">httpx</span> <span class="kn">import</span> <span class="n">AsyncClient</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.mark.asyncio</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">test_add_game</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="n">AsyncClient</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&#34;/game&#34;</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">request_data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="n">status</span><span class="o">.</span><span class="n">HTTP_201_CREATED</span>
</span></span></code></pre></div><p>However socketio at the moment does not provide us with a test client we can use. So we will start and stop a Uvicorn server and send actual
Socketio requests from a Socketio client. There is a Socketio client library we can use to do this, available in Python.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">UvicornTestServer</span><span class="p">(</span><span class="n">uvicorn</span><span class="o">.</span><span class="n">Server</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="n">ASGIApp</span> <span class="o">=</span> <span class="n">main</span><span class="o">.</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">LISTENING_IF</span><span class="p">,</span> <span class="n">port</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">PORT</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Event</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Awaitable</span><span class="p">[</span><span class="n">Any</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">config</span><span class="o">=</span><span class="n">uvicorn</span><span class="o">.</span><span class="n">Config</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="n">port</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">startup</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Override uvicorn startup&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">startup</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">setup_event_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span><span class="o">.</span><span class="n">set</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">start_up</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Start up server asynchronously&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">serve</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span><span class="o">.</span><span class="n">wait</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">tear_down</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Shut down server asynchronously&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">should_exit</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span>
</span></span></code></pre></div><p>This is a test class which we can use to start and stop the Uvicorn server. Note that the class inherits
from <code>uvicorn.server</code>, we need to overwrite the <code>startup()</code> method as we want to change the startup a bit.</p>
<p>Before explaining the code above let&rsquo;s take a look at how we may use it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">event_loop</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_event_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span> <span class="n">loop</span>
</span></span><span class="line"><span class="cl">    <span class="n">loop</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">startup_and_shutdown_server</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">server</span> <span class="o">=</span> <span class="n">UvicornTestServer</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">server</span><span class="o">.</span><span class="n">start_up</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">server</span><span class="o">.</span><span class="n">tear_down</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">client</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">AsyncIterator</span><span class="p">[</span><span class="n">AsyncClient</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="n">sio</span> <span class="o">=</span> <span class="n">socketio</span><span class="o">.</span><span class="n">AsyncClient</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sio</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">BASE_URL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span> <span class="n">sio</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sio</span><span class="o">.</span><span class="n">disconnect</span><span class="p">()</span>
</span></span></code></pre></div><p>What we have done is created two pytest fixtures, the first simply starts an event loop so we can test async code.</p>
<h3 id="tangent-on-asyncio">Tangent on asyncio</h3>
<p>To test async code with pytest we need to install the <code>pyest-asyncio</code> library.
By default this will give us an <code>event_loop</code> fixture that runs on scope of <code>function</code>. So it will start and stop after
each test function. However if you want to use fixtures that aren&rsquo;t of scope <code>function</code> i.e. <code>session</code> or <code>module</code>.
Then we need to redefine the <code>event_loop</code> function as we have done in the example above.</p>
<p>Okay back to our code above. The main bit we are interested in is the <code>startup_and_shutdown_server</code> function, here we
start the server before all of our tests and due to how <code>yield</code>, you can read more about how
<a href="/blog/python-yield-explained/">yield works here</a>, we will stop our server after all of our tests have run.</p>
<p>This happens automatically without calling the function because of the decorator we have provided
<code>@pytest.fixture(autouse=True, scope=&quot;session&quot;)</code>.
Again we are using scope <code>session</code> so that this function isn&rsquo;t called either for
every function (which would slow down our tests). We could&rsquo;ve set it to <code>module</code> but again
if we have multiple test files we don&rsquo;t want to run this function for every file (module).</p>
<h3 id="deeper-diver-into-uvicorntestserver">Deeper diver into UvicornTestServer</h3>
<p>Let&rsquo;s take a look at the first two methods</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl">  <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="n">ASGIApp</span> <span class="o">=</span> <span class="n">main</span><span class="o">.</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">LISTENING_IF</span><span class="p">,</span> <span class="n">port</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">PORT</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">      <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Event</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">      <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Awaitable</span><span class="p">[</span><span class="n">Any</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">      <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">config</span><span class="o">=</span><span class="n">uvicorn</span><span class="o">.</span><span class="n">Config</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="n">port</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">async</span> <span class="k">def</span> <span class="nf">startup</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;&#34;&#34;Override uvicorn startup&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="k">await</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">startup</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">      <span class="bp">self</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">setup_event_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">      <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span><span class="o">.</span><span class="n">set</span><span class="p">()</span>
</span></span></code></pre></div><p>The <code>__init__</code> magic dunder method creates an asyncio event <code>asyncio.Event()</code>. These events are often used to:</p>
<blockquote>
<p>An asyncio event can be used to notify multiple asyncio tasks that some event has happened. - <a href="https://docs.python.org/3/library/asyncio-sync.html#asyncio.Event">https://docs.python.org/3/library/asyncio-sync.html#asyncio.Event</a></p>
</blockquote>
<p>Then we create a variable <code>self._serve_task: Optional[Awaitable[Any]] = None</code>, we will see how this used later.
Finally we call the parent calls <code>__init__</code> method (<code>super().__init__()</code>). This calls the <code>__init__</code> function
of the <code>uvicorn.Server</code> class. We do this to set the <code>uvicorn.Config</code>, which includes our app and which host and port
to start the server.</p>
<p>Onto the second method <code>startup</code> this also overwrites a method in the parent class. In fact the first we do is call
the parent class&rsquo;s <code>startup</code> method (<code>await super().startup()</code>). Then we start the event loop ourselves
<code>self.config.setup_event_loop()</code>, where our web app will run.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Event Loop</summary>
  
  This is a different event loop in which our tests run in.
</details>

<p>Finally we do <code>self._startup_done.set()</code>, we are setting this event as true i.e. is complete. So any coroutines waiting
until this set can be carry on their execution.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Asyncio</summary>
  
  An Event object manages an internal flag that can be set to true with the set() method and reset to false with the clear() method. The wait() method blocks until the flag is set to true. The flag is set to false initially. - <a href="https://docs.python.org/3/library/asyncio-sync.html#asyncio.Event">https://docs.python.org/3/library/asyncio-sync.html#asyncio.Event</a>
</details>

<h4 id="yet-another-tangent-on-run-method">Yet another tangent on run() method</h4>
<p>Now the parent class does have a <code>run</code> method we could use, which would start the event loop for us. This however won&rsquo;t work,
lets pretend we change <code>startup_and_shutdown_server</code> function too look like this (<code>server.run()</code>).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">startup_and_shutdown_server</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">server</span> <span class="o">=</span> <span class="n">UvicornTestServer</span><span class="p">()</span>
</span></span><span class="line hl"><span class="cl">    <span class="k">await</span> <span class="n">server</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">server</span><span class="o">.</span><span class="n">tear_down</span><span class="p">()</span>
</span></span></code></pre></div><p>We would get the following error <code>RuntimeError: asyncio.run() cannot be called from a running event loop</code>. This because if
we take a look at the <code>run</code> method in the parent class it contains something like this line
<code>return asyncio.run(self.serve(...))</code>.</p>
<p>This is why we need to write our own code to handle starting the Uvicorn server.</p>
<h4 id="start_up-and-tear_down"><code>start_up</code> and <code>tear_down</code></h4>
<p>Okay let&rsquo;s move and take a look at the <code>start_up</code> and <code>tear_down</code> methods</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">start_up</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">serve</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span><span class="o">.</span><span class="n">wait</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">tear_down</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">should_exit</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span>
</span></span></code></pre></div><p>Remember these are the two methods we will call in our &ldquo;startup and shutdown&rdquo; fixture. The <code>start_up</code> method, creates a task and assigns it
to our empty variable from the <code>__init__</code> method <code>self._serve_task = asyncio.create_task(self.serve())</code>. It calls the <code>serve</code> method to start
the Uvicorn server.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">What does create_task do?</summary>
  
  It submits the coroutine to run &ldquo;in the background&rdquo;, i.e. concurrently with the current task and all other tasks, switching between them at await points. It returns an awaitable handle called a &ldquo;task&rdquo; which you can also use to cancel the execution of the coroutine. - <a href="https://stackoverflow.com/questions/62528272/what-does-asyncio-create-task-do">https://stackoverflow.com/questions/62528272/what-does-asyncio-create-task-do</a>
</details>

<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">What is a task?</summary>
  
  It&rsquo;s an asyncio construct that tracks execution of a coroutine in a concrete event loop. When you call create_task, you submit a coroutine for execution and receive back a handle. You can await this handle when you actually need the result, or you can never await it, if you don&rsquo;t care about the result. This handle is the task, and it inherits from Future, which makes it awaitable and also provides the lower-level callback-based interface, such as add_done_callback. - <a href="https://stackoverflow.com/questions/62528272/what-does-asyncio-create-task-do">https://stackoverflow.com/questions/62528272/what-does-asyncio-create-task-do</a>
</details>

<p>Then we <code>await self._startup_done.wait()</code>, this is the event we created earlier. It will wait until the <code>set()</code> function
has been called in the in the <code>startup</code> method above.</p>
<p>Now onto the <code>tear_down</code> method where we set the <code>should_exit</code> to true. There is a <code>main_loop</code> method called by our
<code>serve</code> method in the parent class. This <code>main_loop</code> calls an <code>on_tick</code> function which returns if <code>self.should_exit</code> is true.
So the call chain looks like: <code>serve</code> -&gt; <code>main_loop</code> -&gt; <code>on_tick</code>. When on_tick returns <code>should_exist</code> as true, it exits it main loop:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main_loop</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">counter</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line hl"><span class="cl">    <span class="n">should_exit</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">on_tick</span><span class="p">(</span><span class="n">counter</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="ow">not</span> <span class="n">should_exit</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">counter</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">        <span class="n">counter</span> <span class="o">=</span> <span class="n">counter</span> <span class="o">%</span> <span class="mi">864000</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
</span></span><span class="line hl"><span class="cl">        <span class="n">should_exit</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">on_tick</span><span class="p">(</span><span class="n">counter</span><span class="p">)</span>
</span></span></code></pre></div><h3 id="client-fixture">Client Fixture</h3>
<p>Finally lets take a look at our final fixture, here we create a client that can be used to make requests with socketio.
We use a similar technique with <code>yields</code> so we return a socketio client. We will see how this used in one of our tests.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">client</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">AsyncIterator</span><span class="p">[</span><span class="n">AsyncClient</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="n">sio</span> <span class="o">=</span> <span class="n">socketio</span><span class="o">.</span><span class="n">AsyncClient</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sio</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">BASE_URL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span> <span class="n">sio</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sio</span><span class="o">.</span><span class="n">disconnect</span><span class="p">()</span>
</span></span></code></pre></div><h2 id="test_roompy">test_room.py</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">socketio.asyncio_client</span> <span class="kn">import</span> <span class="n">AsyncClient</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.mark.asyncio</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">test_success</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="n">AsyncClient</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">future</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span><span class="o">.</span><span class="n">create_future</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@client.on</span><span class="p">(</span><span class="s2">&#34;BAR&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">future</span><span class="o">.</span><span class="n">set_result</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">emit</span><span class="p">(</span><span class="s2">&#34;FOO&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;haseeb&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">wait_for</span><span class="p">(</span><span class="n">future</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mf">5.0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">result</span> <span class="o">==</span> <span class="p">{</span><span class="s2">&#34;foo&#34;</span><span class="p">:</span> <span class="s2">&#34;haseeb&#34;</span><span class="p">}</span>
</span></span></code></pre></div><p>Since we need to wait for the <code>FOO</code> event to return a <code>BAR</code> event we use a future
to await until we get a response then set the return data in the future</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@client.on</span><span class="p">(</span><span class="s2">&#34;BAR&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">_</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">future</span><span class="o">.</span><span class="n">set_result</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span></code></pre></div><p>We <code>await asyncio.wait_for(future, timeout=5.0)</code> for the future to have data set on it.</p>
<p>That&rsquo;s it, the code itself is fairly simple once everything is setup in <code>conftest</code> to actually do the test.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2021-12-23-testing-a-socketio-web-app-written-in-python/source_code">Example source code</a></li>
<li><a href="https://github.com/miguelgrinberg/python-socketio/issues/332#issuecomment-712928157">Github Issue: UvicornTestServer</a></li>
<li><a href="https://stackoverflow.com/questions/62528272/what-does-asyncio-create-task-do">Async Create Task SO</a></li>
<li><a href="https://gitlab.com/banter-bus/banter-bus-core-api">Real application using this testing pattern</a></li>
</ul>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
