<?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>Backend on Alex Jacobs</title>
    <link>https://alex-jacobs.com/tags/backend/</link>
    <description>Recent content in Backend on Alex Jacobs</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 28 Jan 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://alex-jacobs.com/tags/backend/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Deep Dive into Python Async Programming</title>
      <link>https://alex-jacobs.com/posts/pythonasync/</link>
      <pubDate>Sun, 28 Jan 2024 00:00:00 +0000</pubDate>
      
      <guid>https://alex-jacobs.com/posts/pythonasync/</guid>
      <description>A deep dive into Python&amp;#39;s asynchronous programming model, exploring the event loop, coroutines, tasks, futures, and the under-the-hood mechanisms that make it all work.</description>
      <content:encoded><![CDATA[<h2 id="introduction-beyond-synchronous-shores">Introduction: Beyond Synchronous Shores</h2>
<p>In the ever-evolving landscape of software development, responsiveness and efficiency are paramount.  Modern applications, especially those dealing with network requests, user interfaces, or concurrent operations, often demand the ability to handle multiple tasks seemingly at the same time.  For a long time, traditional threading and multiprocessing were the go-to solutions in Python for achieving concurrency.  However, Python&rsquo;s Global Interpreter Lock (GIL) and the overhead associated with thread management can sometimes limit the effectiveness of these approaches, especially for I/O-bound tasks.</p>
<p>Enter asynchronous programming. Async Python offers a powerful paradigm for writing concurrent code that is both efficient and, arguably, more intuitive for certain types of applications.  If you&rsquo;ve encountered the keywords <code>async</code> and <code>await</code> in Python and found yourself intrigued but also slightly mystified, you&rsquo;re not alone.  While the surface-level syntax of async Python might seem straightforward, understanding what&rsquo;s happening beneath the hood is crucial for truly leveraging its power and avoiding common pitfalls.</p>
<p>This post is your comprehensive guide to demystifying async Python.  We&rsquo;ll go far beyond the basic syntax, diving deep into the core concepts that underpin asynchronous programming in Python.  We&rsquo;ll explore the event loop, coroutines, tasks, futures, context switching, and even touch upon how async compares to traditional threading and parallelism.  By the end of this journey, you&rsquo;ll not only be able to write async Python code, but you&rsquo;ll also possess a solid understanding of the mechanisms that make it all tick.</p>
<h2 id="the-async-and-await-duo--the-face-of-async-python">The <code>async</code> and <code>await</code> Duo:  The Face of Async Python</h2>
<p>Let&rsquo;s start with the syntax that you&rsquo;ll encounter most frequently when working with async Python: the <code>async</code> and <code>await</code> keywords. These two are the fundamental building blocks for writing asynchronous code in Python.</p>
<h3 id="defining-asynchronous-functions-with-async-def">Defining Asynchronous Functions with <code>async def</code></h3>
<p>In Python, you declare a function as asynchronous by using the <code>async def</code> syntax instead of the regular <code>def</code>.  This seemingly small change has profound implications.  An <code>async def</code> function, also known as a <strong>coroutine function</strong>, doesn&rsquo;t execute like a regular synchronous function. Instead, it returns a <strong>coroutine object</strong>.</p>
<p>Think of a coroutine object as a promise of work to be done later. It&rsquo;s not the work itself, but rather a representation of that work, ready to be executed when the time is right.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">hello_async</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Hello from async function!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><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="mi">1</span><span class="p">)</span> <span class="c1"># Simulate some async operation (like network I/O)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Async function finished.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># Calling the async function returns a coroutine object</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">coro</span> <span class="o">=</span> <span class="n">hello_async</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Coroutine object: </span><span class="si">{</span><span class="n">coro</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># To actually run the coroutine, we need to use an event loop</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">coro</span><span class="p">)</span>
</span></span></code></pre></div><p>In this example, <code>hello_async</code> is an asynchronous function.  When we call <code>hello_async()</code>, it doesn&rsquo;t immediately print &ldquo;Hello from async function!&rdquo;. Instead, it creates and returns a coroutine object. To actually execute the code within the coroutine, we need to use <code>asyncio.run()</code>, which sets up and runs an <strong>event loop</strong> (more on event loops shortly) to manage the execution of our coroutine.</p>
<h3 id="pausing-execution-with-await">Pausing Execution with <code>await</code></h3>
<p>The <code>await</code> keyword is the other half of the async duo.  It can only be used inside <code>async def</code> functions.  <code>await</code> is the point where an asynchronous function can <strong>pause</strong> its execution and yield control back to the event loop.  This is the crucial mechanism that enables concurrency in async Python without relying on threads.</p>
<p>When you <code>await</code> something, you are essentially saying: &ldquo;I need to wait for this asynchronous operation to complete.  While I&rsquo;m waiting, I&rsquo;m going to yield control back to the event loop so it can work on other tasks. Once this operation is done, please resume my execution from right here.&rdquo;</p>
<p>In our <code>hello_async</code> example, <code>await asyncio.sleep(1)</code> is a simulated asynchronous operation that represents waiting for 1 second.  During this second, the <code>hello_async</code> coroutine pauses, and the event loop is free to execute other coroutines or handle other events.  Once the sleep duration is over, the event loop resumes the <code>hello_async</code> coroutine from the line after the <code>await</code> statement, and it prints &ldquo;Async function finished.&rdquo;</p>
<p><strong>Important Note:</strong>  You can only <code>await</code> objects that are <strong>awaitable</strong>.  In practice, this usually means you&rsquo;re awaiting other coroutines, <code>Future</code> objects (which we&rsquo;ll discuss later), or objects that have implemented the <code>__await__</code> special method.  Standard synchronous functions are <em>not</em> awaitable.</p>
<h2 id="the-event-loop-the-orchestrator-of-asynchronicity">The Event Loop: The Orchestrator of Asynchronicity</h2>
<p>At the heart of async Python lies the <strong>event loop</strong>.  Think of the event loop as the central conductor of an orchestra.  It&rsquo;s responsible for managing and scheduling the execution of all your asynchronous tasks.  It&rsquo;s a single-threaded loop that constantly monitors for events and dispatches tasks to be executed when those events occur.</p>
<h3 id="how-the-event-loop-works-simplified">How the Event Loop Works (Simplified)</h3>
<ol>
<li><strong>Task Queue:</strong> The event loop maintains a queue of tasks (usually coroutines wrapped in <code>Task</code> objects) that are ready to be executed or resumed.</li>
<li><strong>Event Monitoring:</strong> The event loop also monitors for various events, such as network sockets becoming ready for reading or writing, timers expiring, or file operations completing.  It typically uses efficient system calls like <code>select</code>, <code>poll</code>, or <code>epoll</code> (depending on the operating system) to monitor these events without blocking.</li>
<li><strong>Task Execution and Resumption:</strong> When an event occurs that makes a task ready to proceed (e.g., data is available on a socket that a task is waiting to read from), the event loop picks up that task from the queue and executes it until it encounters an <code>await</code> statement.</li>
<li><strong>Yielding Control with <code>await</code>:</strong> When a coroutine reaches an <code>await</code> statement, it effectively tells the event loop, &ldquo;I need to wait for this operation.  Please pause me and let someone else run.&rdquo;  The event loop then takes control and looks for other tasks in the queue that are ready to run.</li>
<li><strong>Resuming Execution:</strong> Once the awaited operation completes (e.g., the network request returns, the timer expires), the event loop is notified.  It then puts the paused coroutine back into the task queue, ready to be resumed at the point where it left off.</li>
<li><strong>Looping Continuously:</strong> The event loop continues this process of monitoring events, executing tasks, and pausing/resuming coroutines in a loop until there are no more tasks to run or the program is explicitly stopped.</li>
</ol>
<h3 id="different-event-loop-implementations-asyncio-uvloop-and-more">Different Event Loop Implementations: <code>asyncio</code>, <code>uvloop</code>, and More</h3>
<p>Python&rsquo;s standard library provides the <code>asyncio</code> module, which includes a built-in event loop implementation. This default event loop is written in Python and is generally sufficient for many use cases.</p>
<p>However, for performance-critical applications, especially those dealing with high-performance networking, you might consider using alternative event loop implementations.  One popular option is <strong><code>uvloop</code></strong>.</p>
<p><strong><code>uvloop</code></strong>: <code>uvloop</code> is a blazing-fast, drop-in replacement for <code>asyncio</code>&rsquo;s event loop. It&rsquo;s written in Cython and built on top of <code>libuv</code>, the same high-performance library that powers Node.js.  <code>uvloop</code> is significantly faster than the default <code>asyncio</code> event loop, especially for network I/O.</p>
<p>To use <code>uvloop</code>, you typically need to install it separately (<code>pip install uvloop</code>) and then set it as the event loop policy for <code>asyncio</code> when your application starts:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">uvloop</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Running with uvloop!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><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="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Done.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><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="ln">10</span><span class="cl">    <span class="n">uvloop</span><span class="o">.</span><span class="n">install</span><span class="p">()</span> <span class="c1"># Set uvloop as the event loop policy</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</span></span></code></pre></div><p>Other event loop implementations exist, but <code>asyncio</code> and <code>uvloop</code> are the most commonly used in Python async programming.  Choosing between them often depends on the performance requirements of your application. For most general async tasks, <code>asyncio</code>&rsquo;s default loop is perfectly adequate.  For high-load network applications, <code>uvloop</code> can provide a noticeable performance boost.</p>
<h2 id="layers-of-async-python-coroutines-tasks-and-futures">Layers of Async Python: Coroutines, Tasks, and Futures</h2>
<p>To truly understand async Python, it&rsquo;s helpful to think about it in layers.  We&rsquo;ve already touched upon coroutines and the event loop. Let&rsquo;s now delve into the roles of <strong>Tasks</strong> and <strong>Futures</strong>.</p>
<h3 id="coroutines-the-asynchronous-functions">Coroutines: The Asynchronous Functions</h3>
<p>As we discussed earlier, coroutines are the asynchronous functions you define using <code>async def</code>. They represent units of asynchronous work.  Coroutines themselves are not directly executed by the event loop. Instead, they need to be wrapped in something that the event loop can manage and schedule.  This &ldquo;something&rdquo; is a <strong>Task</strong>.</p>
<h3 id="tasks-scheduling-coroutines-in-the-event-loop">Tasks: Scheduling Coroutines in the Event Loop</h3>
<p>A <strong>Task</strong> in <code>asyncio</code> is essentially a wrapper around a coroutine that allows the event loop to schedule and manage its execution.  When you want to run a coroutine concurrently within the event loop, you typically create a Task from it.</p>
<p>You can create a Task using <code>asyncio.create_task()</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">my_coroutine</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Coroutine started&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><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="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Coroutine finished&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">return</span> <span class="s2">&#34;Coroutine result&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">task1</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="n">my_coroutine</span><span class="p">())</span> <span class="c1"># Create a Task from the coroutine</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">task2</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="n">my_coroutine</span><span class="p">())</span> <span class="c1"># Create another Task</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Tasks created, waiting for completion...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">result1</span> <span class="o">=</span> <span class="k">await</span> <span class="n">task1</span> <span class="c1"># Await the completion of task1</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">result2</span> <span class="o">=</span> <span class="k">await</span> <span class="n">task2</span> <span class="c1"># Await the completion of task2</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Task 1 result: </span><span class="si">{</span><span class="n">result1</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Task 2 result: </span><span class="si">{</span><span class="n">result2</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</span></span></code></pre></div><p>In this example, we create two Tasks, <code>task1</code> and <code>task2</code>, from the same <code>my_coroutine</code>.  <code>asyncio.create_task()</code> schedules these coroutines to be run by the event loop concurrently. When we <code>await task1</code> and <code>await task2</code>, we are waiting for these Tasks to complete and retrieve their results.</p>
<p>Tasks are essential for managing the lifecycle of coroutines within the event loop. They provide methods to:</p>
<ul>
<li><strong>Cancel a Task:</strong>  <code>task.cancel()</code></li>
<li><strong>Check if a Task is done:</strong> <code>task.done()</code></li>
<li><strong>Get the result of a Task:</strong> <code>task.result()</code> (if done)</li>
<li><strong>Get exceptions raised during Task execution:</strong> <code>task.exception()</code> (if any)</li>
</ul>
<h3 id="futures-representing-the-result-of-asynchronous-operations">Futures: Representing the Result of Asynchronous Operations</h3>
<p>A <strong>Future</strong> is an object that represents the eventual result of an asynchronous operation.  It&rsquo;s a placeholder for a value that might not be available yet.  Tasks in <code>asyncio</code> are actually a subclass of Futures.</p>
<p>Futures are used extensively in async Python to represent the outcome of operations that are performed asynchronously, such as:</p>
<ul>
<li><strong>Network I/O:</strong>  Reading data from a socket, sending a request to a server.</li>
<li><strong>File I/O:</strong> Reading or writing to a file (in an async context).</li>
<li><strong>Concurrent computations:</strong>  Tasks running in parallel (within the same event loop or in different threads/processes).</li>
</ul>
<p>A Future object has a state that can be:</p>
<ul>
<li><strong>Pending:</strong> The asynchronous operation is still in progress.</li>
<li><strong>Running:</strong>  The operation is currently being executed.</li>
<li><strong>Done:</strong> The operation has completed successfully or with an exception.</li>
<li><strong>Cancelled:</strong> The operation has been cancelled.</li>
</ul>
<p>You can interact with a Future to:</p>
<ul>
<li><strong>Check if it&rsquo;s done:</strong> <code>future.done()</code></li>
<li><strong>Get the result:</strong> <code>future.result()</code> (blocks until done if pending, raises exception if an exception occurred)</li>
<li><strong>Get exceptions:</strong> <code>future.exception()</code> (returns exception if one occurred, otherwise <code>None</code>)</li>
<li><strong>Add callbacks:</strong> <code>future.add_done_callback(callback_function)</code> (run a function when the future is done)</li>
<li><strong>Cancel the future:</strong> <code>future.cancel()</code></li>
</ul>
<p>When you <code>await</code> a Task (or any awaitable Future-like object), you are essentially waiting for that Future to become &ldquo;done&rdquo; and then retrieving its result or handling any exceptions.</p>
<h2 id="coroutine-execution-and-context-switching-the-asynchronous-dance">Coroutine Execution and Context Switching: The Asynchronous Dance</h2>
<p>Now, let&rsquo;s delve deeper into how coroutines are actually executed and how context switching works in async Python.</p>
<h3 id="cooperative-multitasking-and-context-switching">Cooperative Multitasking and Context Switching</h3>
<p>Async Python uses <strong>cooperative multitasking</strong>.  This is in contrast to <strong>preemptive multitasking</strong> used by operating systems for threads and processes.</p>
<ul>
<li><strong>Preemptive Multitasking (Threads/Processes):</strong> In preemptive multitasking, the operating system&rsquo;s scheduler decides when to switch between threads or processes.  It can interrupt a running thread/process at any time and switch to another, even if the running thread/process doesn&rsquo;t explicitly yield control.  This is typically based on time slices and priority levels.</li>
<li><strong>Cooperative Multitasking (Async Python):</strong> In cooperative multitasking, coroutines voluntarily yield control back to the event loop when they encounter an <code>await</code> statement.  The event loop then decides which coroutine to run next.  Context switching only happens at these explicit <code>await</code> points.  A coroutine will continue to run until it reaches an <code>await</code> or completes.</li>
</ul>
<p>This cooperative nature has important implications:</p>
<ul>
<li><strong>No True Parallelism (within a single event loop):</strong>  Within a single event loop running in a single thread, true parallelism is not achieved.  Coroutines take turns running.  If a coroutine doesn&rsquo;t <code>await</code> frequently and performs long-running CPU-bound operations, it can block the event loop and prevent other coroutines from making progress.</li>
<li><strong>Responsiveness:</strong> Cooperative multitasking is excellent for I/O-bound tasks. While one coroutine is waiting for I/O, another can run, keeping the application responsive.</li>
<li><strong>Less Overhead:</strong> Context switching in cooperative multitasking is generally lighter than preemptive context switching between threads or processes. There&rsquo;s less operating system overhead involved.</li>
<li><strong>Deterministic Behavior (mostly):</strong> Because context switching happens only at explicit <code>await</code> points, the execution flow of async code is often more predictable and easier to reason about compared to multithreaded code, which can have race conditions and unpredictable scheduling.</li>
</ul>
<h3 id="how-coroutines-are-paused-and-resumed">How Coroutines are Paused and Resumed</h3>
<p>When a coroutine reaches an <code>await</code> statement, several things happen:</p>
<ol>
<li><strong><code>await</code> Expression:</strong> The expression after <code>await</code> (e.g., <code>asyncio.sleep(1)</code>, another coroutine, a Future) must be awaitable.</li>
<li><strong>Yielding Control:</strong> The coroutine effectively &ldquo;pauses&rdquo; its execution at the <code>await</code> point. It returns control back to the event loop.</li>
<li><strong>Event Loop Takes Over:</strong> The event loop becomes active again. It looks at its task queue for other tasks that are ready to run.</li>
<li><strong>Registering for Resumption:</strong>  The coroutine, along with information about where it paused (the line after the <code>await</code>), is registered with the event loop as being &ldquo;waiting&rdquo; for the completion of the awaited operation.</li>
<li><strong>Awaited Operation Proceeds:</strong> The awaited operation (e.g., network request, timer) proceeds asynchronously in the background (often managed by non-blocking system calls).</li>
<li><strong>Event Notification:</strong> When the awaited operation is complete, the event loop receives a notification (e.g., socket becomes readable, timer expires).</li>
<li><strong>Resuming the Coroutine:</strong> The event loop puts the paused coroutine back into the task queue, marked as ready to be resumed.</li>
<li><strong>Coroutine Resumes:</strong> When the event loop gets around to executing this coroutine again, it resumes from the exact point where it was paused (right after the <code>await</code> statement).  It now has access to the result of the awaited operation (if any).</li>
</ol>
<p>This pause-and-resume mechanism is what allows asynchronous code to be written in a seemingly sequential style, even though it&rsquo;s actually being executed in an interleaved and non-blocking manner.</p>
<h2 id="async-vs-threads-vs-processes-choosing-the-right-tool">Async vs. Threads vs. Processes: Choosing the Right Tool</h2>
<p>It&rsquo;s crucial to understand when async Python is the right choice and when traditional threading or multiprocessing might be more appropriate.</p>
<h3 id="async-python-ideal-for-io-bound-tasks">Async Python: Ideal for I/O-Bound Tasks</h3>
<p>Async Python excels in scenarios where your application is <strong>I/O-bound</strong>.  This means that the primary bottleneck is waiting for external operations to complete, such as:</p>
<ul>
<li><strong>Network requests:</strong>  Fetching data from APIs, making HTTP requests, communicating with databases over a network.</li>
<li><strong>File I/O:</strong>  Reading and writing to files (especially over a network file system).</li>
<li><strong>Waiting for user input:</strong>  In GUI applications or interactive systems.</li>
</ul>
<p>In these cases, the CPU is often idle while waiting for I/O operations. Async Python allows you to utilize this idle time by letting other coroutines run while one is waiting for I/O.  It&rsquo;s highly efficient for handling many concurrent I/O operations with minimal overhead.</p>
<p><strong>Example: Web Server</strong></p>
<p>A web server that handles many concurrent requests is a classic example where async Python shines.  While one request is being processed (which often involves waiting for database queries, external API calls, etc.), the server can be handling other requests concurrently.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">aiohttp</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">aiohttp</span> <span class="kn">import</span> <span class="n">web</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">fetch_data_from_api</span><span class="p">(</span><span class="n">api_url</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="o">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">api_url</span><span class="p">)</span> <span class="k">as</span> <span class="n">response</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="k">return</span> <span class="k">await</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">handler</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">fetch_data_from_api</span><span class="p">(</span><span class="s2">&#34;https://api.example.com/data&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">return</span> <span class="n">web</span><span class="o">.</span><span class="n">json_response</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">app</span> <span class="o">=</span> <span class="n">web</span><span class="o">.</span><span class="n">Application</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">app</span><span class="o">.</span><span class="n">add_routes</span><span class="p">([</span><span class="n">web</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">,</span> <span class="n">handler</span><span class="p">)])</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">runner</span> <span class="o">=</span> <span class="n">web</span><span class="o">.</span><span class="n">AppRunner</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">await</span> <span class="n">runner</span><span class="o">.</span><span class="n">setup</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">site</span> <span class="o">=</span> <span class="n">web</span><span class="o">.</span><span class="n">TCPSite</span><span class="p">(</span><span class="n">runner</span><span class="p">,</span> <span class="s1">&#39;localhost&#39;</span><span class="p">,</span> <span class="mi">8080</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">await</span> <span class="n">site</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Server started at http://localhost:8080&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Event</span><span class="p">()</span><span class="o">.</span><span class="n">wait</span><span class="p">()</span> <span class="c1"># Keep the server running indefinitely</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><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="ln">25</span><span class="cl">    <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</span></span></code></pre></div><p>This example uses <code>aiohttp</code>, an async HTTP client and server library.  The <code>fetch_data_from_api</code> coroutine performs an asynchronous HTTP request.  The <code>handler</code> coroutine uses <code>await fetch_data_from_api</code> to fetch data without blocking the server.  The server can handle many requests concurrently, making it highly scalable for I/O-bound web applications.</p>
<h3 id="threads-for-cpu-bound-tasks-and-true-parallelism-within-limits">Threads: For CPU-Bound Tasks and True Parallelism (within limits)</h3>
<p>Threads, especially when used with Python&rsquo;s <code>threading</code> module, are suitable for tasks that are more <strong>CPU-bound</strong> and can benefit from <strong>concurrency</strong> (even if not true parallelism due to the GIL).</p>
<ul>
<li>
<p><strong>CPU-Bound Tasks:</strong> Tasks that spend most of their time performing computations on the CPU, rather than waiting for I/O.  Examples include:</p>
<ul>
<li>Image processing</li>
<li>Numerical computations</li>
<li>Data analysis</li>
<li>Cryptographic operations</li>
</ul>
</li>
<li>
<p><strong>Concurrency (with GIL limitations):</strong> Python&rsquo;s GIL (Global Interpreter Lock) prevents true parallelism for CPU-bound tasks in standard CPython threads. Only one thread can hold the Python interpreter lock at any given time.  However, threads can still provide concurrency by releasing the GIL during I/O operations or certain blocking system calls.  This can improve responsiveness even for CPU-bound tasks if they involve some I/O or blocking.</p>
</li>
</ul>
<p><strong>Example: CPU-Bound Computation (with threading for concurrency)</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">threading</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">cpu_bound_task</span><span class="p">(</span><span class="n">task_id</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Task </span><span class="si">{</span><span class="n">task_id</span><span class="si">}</span><span class="s2"> started&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">start_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="o">**</span><span class="mi">7</span><span class="p">):</span> <span class="c1"># Simulate CPU-intensive computation</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">result</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">end_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">duration</span> <span class="o">=</span> <span class="n">end_time</span> <span class="o">-</span> <span class="n">start_time</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Task </span><span class="si">{</span><span class="n">task_id</span><span class="si">}</span><span class="s2"> finished in </span><span class="si">{</span><span class="n">duration</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> seconds, result: </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">def</span> <span class="nf">main_threads</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">threads</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">thread</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">cpu_bound_task</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">i</span><span class="p">,))</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">threads</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">thread</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">thread</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">for</span> <span class="n">thread</span> <span class="ow">in</span> <span class="n">threads</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">thread</span><span class="o">.</span><span class="n">join</span><span class="p">()</span> <span class="c1"># Wait for all threads to complete</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><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="ln">25</span><span class="cl">    <span class="n">main_threads</span><span class="p">()</span>
</span></span></code></pre></div><p>In this example, <code>cpu_bound_task</code> simulates a CPU-intensive operation.  We create multiple threads to run this task concurrently.  While the GIL limits true parallelism for CPU-bound Python code, threads can still provide some concurrency and potential performance improvement, especially if the tasks involve some I/O or blocking operations.  For purely CPU-bound tasks, however, the benefits might be limited by the GIL.</p>
<h3 id="processes-true-parallelism-and-cpu-bound-tasks-but-heavier">Processes: True Parallelism and CPU-Bound Tasks (but heavier)</h3>
<p>For truly <strong>CPU-bound and computationally intensive tasks</strong> that need <strong>true parallelism</strong> and to bypass the GIL limitations, <strong>multiprocessing</strong> using Python&rsquo;s <code>multiprocessing</code> module is the way to go.</p>
<ul>
<li><strong>True Parallelism:</strong> Multiprocessing creates separate processes, each with its own Python interpreter and memory space. Processes run in parallel on multiple CPU cores, achieving true parallelism for CPU-bound tasks.</li>
<li><strong>Bypassing the GIL:</strong> Each process has its own GIL, so the GIL limitation of threads is overcome.</li>
<li><strong>Higher Overhead:</strong> Process creation and inter-process communication have more overhead compared to threads or async tasks. Processes consume more system resources (memory, process management overhead).</li>
</ul>
<p><strong>Example: CPU-Bound Computation (with multiprocessing for parallelism)</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">multiprocessing</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">cpu_bound_task_process</span><span class="p">(</span><span class="n">task_id</span><span class="p">):</span> <span class="c1"># Same CPU-bound task as before</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Process </span><span class="si">{</span><span class="n">task_id</span><span class="si">}</span><span class="s2"> started&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">start_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="o">**</span><span class="mi">7</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">result</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">end_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">duration</span> <span class="o">=</span> <span class="n">end_time</span> <span class="o">-</span> <span class="n">start_time</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Process </span><span class="si">{</span><span class="n">task_id</span><span class="si">}</span><span class="s2"> finished in </span><span class="si">{</span><span class="n">duration</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> seconds, result: </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">def</span> <span class="nf">main_processes</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">processes</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">process</span> <span class="o">=</span> <span class="n">multiprocessing</span><span class="o">.</span><span class="n">Process</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">cpu_bound_task_process</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">i</span><span class="p">,))</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">processes</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">process</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">process</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">for</span> <span class="n">process</span> <span class="ow">in</span> <span class="n">processes</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">process</span><span class="o">.</span><span class="n">join</span><span class="p">()</span> <span class="c1"># Wait for all processes to complete</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><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="ln">25</span><span class="cl">    <span class="n">main_processes</span><span class="p">()</span>
</span></span></code></pre></div><p>In this multiprocessing example, we create separate processes to run the same CPU-bound task.  Because each process has its own interpreter and bypasses the GIL, we can achieve true parallelism and significantly speed up CPU-intensive computations on multi-core systems.</p>
<h3 id="choosing-the-right-concurrency-approach">Choosing the Right Concurrency Approach</h3>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Async Python (asyncio)</th>
<th>Threads (threading)</th>
<th>Processes (multiprocessing)</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Task Type</strong></td>
<td>I/O-bound</td>
<td>CPU-bound (with I/O)</td>
<td>CPU-bound (pure computation)</td>
</tr>
<tr>
<td><strong>Parallelism</strong></td>
<td>No (within loop)</td>
<td>Limited (due to GIL)</td>
<td>Yes (true parallelism)</td>
</tr>
<tr>
<td><strong>Concurrency</strong></td>
<td>High</td>
<td>Moderate</td>
<td>High</td>
</tr>
<tr>
<td><strong>Overhead</strong></td>
<td>Low</td>
<td>Moderate</td>
<td>High</td>
</tr>
<tr>
<td><strong>GIL Impact</strong></td>
<td>Not affected</td>
<td>Limited by GIL</td>
<td>Bypasses GIL</td>
</tr>
<tr>
<td><strong>Context Switching</strong></td>
<td>Cooperative (light)</td>
<td>Preemptive (OS)</td>
<td>Preemptive (OS)</td>
</tr>
<tr>
<td><strong>Memory Footprint</strong></td>
<td>Lower</td>
<td>Moderate</td>
<td>Higher</td>
</tr>
<tr>
<td><strong>Complexity</strong></td>
<td>Moderate</td>
<td>Moderate</td>
<td>Higher (IPC needed)</td>
</tr>
<tr>
<td><strong>Use Cases</strong></td>
<td>Web servers, network apps, UI</td>
<td>Concurrent I/O + CPU</td>
<td>CPU-intensive computations</td>
</tr>
</tbody>
</table>
<p><strong>General Guidelines:</strong></p>
<ul>
<li><strong>I/O-Bound, High Concurrency:</strong> Async Python (asyncio) is often the best choice.</li>
<li><strong>CPU-Bound with some I/O, Responsiveness:</strong> Threads (threading) can be considered, but be mindful of GIL limitations for pure CPU-bound tasks.</li>
<li><strong>CPU-Bound, True Parallelism, Max Performance:</strong> Processes (multiprocessing) are essential, especially for computationally intensive tasks on multi-core machines.</li>
<li><strong>Hybrid Applications:</strong> You can combine async and multiprocessing. For example, use async for handling network I/O and multiprocessing for CPU-bound background tasks.</li>
</ul>
<h2 id="conclusion-embracing-the-asynchronous-world">Conclusion: Embracing the Asynchronous World</h2>
<p>Async Python offers a powerful and elegant way to write concurrent code, particularly for I/O-bound applications.  Understanding the underlying mechanisms – the event loop, coroutines, tasks, futures, and cooperative multitasking – is key to effectively leveraging its benefits.</p>
<p>While async Python is not a silver bullet for all concurrency problems, and it&rsquo;s not a direct replacement for threading or multiprocessing in all cases, it provides a compelling and often more efficient alternative for many modern application scenarios.  By mastering async Python, you gain a valuable tool in your development arsenal, enabling you to build responsive, scalable, and performant applications in the asynchronous world.  So, embrace the <code>async</code> and <code>await</code> duo, dive into the event loop, and unlock the power of asynchronous programming in Python!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Mastering Integration Testing with FastAPI</title>
      <link>https://alex-jacobs.com/posts/fastapitests/</link>
      <pubDate>Sun, 11 Jun 2023 00:00:00 +0000</pubDate>
      
      <guid>https://alex-jacobs.com/posts/fastapitests/</guid>
      <description>Integration Testing FastAPI: Harnessing the Power of Mocking Backend Services with MongoMock, MockS3, and More</description>
      <content:encoded><![CDATA[<p>Follow along with all the code <a href="https://github.com/alexjacobs08/fastApi-Integration-tests">here</a></p>
<h2 id="introduction">Introduction</h2>
<p>If you&rsquo;re a backend developer working with FastAPI, you already know the framework excels at simplifying API development
with features like async support and automated Swagger docs. However, when it comes to integration testing&ndash;particularly
mocking external services like MongoDB, AWS S3, and third-party APIs—the waters can get murky. This post is your guide
to navigating these complexities.</p>
<p>This isn&rsquo;t a one-stop-shop for all things testing; the focus is squarely on integration testing within
FastAPI.</p>
<p><strong>Prerequisites:</strong> Familiarity with FastAPI, PyTest, MongoDB, and AWS S3 is assumed. If you&rsquo;re new to any of these
technologies, you may want to get up to speed before proceeding.</p>
<h3 id="what-is-integration-testing">What is Integration Testing?</h3>
<p>Integration testing involves combining individual units of code and testing them as a group. This type of testing aims
to expose faults in the interactions between integrated units. In our context, we could also probably call these
API tests (and you&rsquo;ll see that&rsquo;s how I name my test files). In the context of FastAPI, these units often involve your
API endpoints, external databases like MongoDB, and other services such as AWS S3.</p>
<h3 id="why-its-challenging">Why It&rsquo;s Challenging</h3>
<p>When compared to unit tests, integration tests in FastAPI present unique difficulties. These challenges mostly stem from
the interaction with external dependencies. Mocking these dependencies is not always straightforward, and mistakes can
lead to false positives or negatives, undermining the purpose of the tests.</p>
<h3 id="why-it-matters">Why It Matters</h3>
<p>So, why focus on integration testing in FastAPI? Because it&rsquo;s here that you validate that various services and databases
work in harmony. By ensuring that these integrated units function as expected, you not only increase code reliability
but also save debugging time in the long run.</p>
<p>Unit tests are great for testing individual units of code, but they don&rsquo;t test the interactions between these units.
I also find integration tests (especially this kind, which test our endpoints) are particularly useful before doing a
large refactor&ndash;going from a v0 to v1 of an app, where unit tests might not make a ton of sense because you&rsquo;re rewriting
all of your &lsquo;units&rsquo;.</p>
<h2 id="a-glimpse-into-our-test-app">A Glimpse into Our Test App</h2>
<p>Before we delve into the technicalities of mocking various elements, let&rsquo;s take a quick look at the FastAPI application
we&rsquo;ll be using as a test subject. This app serves as a sandbox for our integration tests, built to showcase different
aspects of FastAPI that commonly require mocking in a test environment.</p>
<p>Our sample application has a straightforward schema designed for demonstration purposes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="l">/login </span><span class="w"> </span><span class="c"># simple login</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">  </span><span class="nt">POST</span><span class="p">:</span><span class="w"> </span><span class="l">Login</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="l">/users/me/ </span><span class="w"> </span><span class="c"># example for mocking auth</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">  </span><span class="nt">GET</span><span class="p">:</span><span class="w"> </span><span class="l">Read Users Me</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w"></span><span class="l">/users/me/preferences </span><span class="w"> </span><span class="c"># example for mocking mongodB</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">  </span><span class="nt">GET</span><span class="p">:</span><span class="w"> </span><span class="l">Get Preferences</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">  </span><span class="nt">POST</span><span class="p">:</span><span class="w"> </span><span class="l">Save Preferences</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w"></span><span class="l">/users/me/profile_pic </span><span class="w"> </span><span class="c"># example for mocking s3</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">  </span><span class="nt">GET</span><span class="p">:</span><span class="w"> </span><span class="l">Get Profile Picture</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">  </span><span class="nt">POST</span><span class="p">:</span><span class="w"> </span><span class="l">Save Profile Picture</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w"></span><span class="l">/weather/{city} </span><span class="w"> </span><span class="c"># example for mocking external API</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">  </span><span class="nt">GET</span><span class="p">:</span><span class="w"> </span><span class="l">Get Weather</span><span class="w">
</span></span></span></code></pre></div><p>Our tests live in <code>tests/</code> where they are organized according to the application components they
evaluate. We employ PyTest for test execution and adhere to its conventions for discovering tests and initial set up.
This includes utilizing the <code>conftest.py</code> configuration file for shared hooks and fixtures.</p>
<h2 id="mocking-authentication-in-fastapi">Mocking Authentication in FastAPI</h2>
<p>As we move through the article, we&rsquo;ll see that FastAPI provides an easy way to do dependency injection,
which is especially useful for our testing scenarios. Using <code>dependency_overrides</code>, we can inject our mocked functions
into the FastAPI app, but remember, this only works for endpoints/functions using the <code>Depends()</code> syntax.
You can read more about dependency injection in FastAPI <a href="https://fastapi.tiangolo.com/tutorial/dependencies/">here</a>.</p>
<p>In our FastAPI application, we have endpoints that require authentication. During testing, we don&rsquo;t want to use real
authentication because it would require us to manage real user credentials and tokens. This would complicate our tests
and potentially expose sensitive information. Therefore, we mock the authentication process.</p>
<h3 id="testing-our-login">Testing Our Login</h3>
<p>Before we get to dependency injection or mocking out our services, let&rsquo;s make sure our auth works. We have simple
authentication defined in the project that checks if a user is in the database, if their password
matches, and returns a token if they are. (I am not going to focus on how to do authentication, but you can check out
the code in <code>auth.py</code> if you&rsquo;re interested.)</p>
<p>For the purposes of this tutorial, our database is just a dictionary with one hard coded user.</p>
<h6 id="ill-be-using-my-name-as-the-username-in-this-tutorial-in-order-to-burn-it-into-your-brain">(I&rsquo;ll be using my name as the username in this tutorial in order to burn it into your brain)</h6>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: app/db.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">users_db</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">UserInDB</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="s2">&#34;alexjacobs&#34;</span><span class="p">:</span> <span class="n">UserInDB</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="n">username</span><span class="o">=</span><span class="s2">&#34;alexjacobs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="n">email</span><span class="o">=</span><span class="s2">&#34;alex@example.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="n">hashed_password</span><span class="o">=</span><span class="n">get_password_hash</span><span class="p">(</span><span class="s2">&#34;secret&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>and our login endpoint&hellip;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># File: app/main.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nd">@app</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&#34;/login&#34;</span><span class="p">,</span> <span class="n">response_model</span><span class="o">=</span><span class="n">Token</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">login</span><span class="p">(</span><span class="n">_login</span><span class="p">:</span> <span class="n">Login</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">user</span> <span class="o">=</span> <span class="n">authenticate_user</span><span class="p">(</span><span class="n">users_db</span><span class="p">,</span> <span class="n">_login</span><span class="o">.</span><span class="n">username</span><span class="p">,</span> <span class="n">_login</span><span class="o">.</span><span class="n">password</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">user</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">raise</span> <span class="n">HTTPException</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="n">status_code</span><span class="o">=</span><span class="n">status</span><span class="o">.</span><span class="n">HTTP_401_UNAUTHORIZED</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="n">detail</span><span class="o">=</span><span class="s2">&#34;Incorrect username or password&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;WWW-Authenticate&#34;</span><span class="p">:</span> <span class="s2">&#34;Bearer&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">access_token</span> <span class="o">=</span> <span class="n">create_access_token</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;sub&#34;</span><span class="p">:</span> <span class="n">user</span><span class="o">.</span><span class="n">username</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;jwt&#34;</span><span class="p">:</span> <span class="n">access_token</span><span class="p">,</span> <span class="s2">&#34;token_type&#34;</span><span class="p">:</span> <span class="s2">&#34;bearer&#34;</span><span class="p">}</span>
</span></span></code></pre></div><p>and we have some simple tests just to verify that our login endpoint works as expected</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># File: tests/test_auth_api.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">test_login</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&#34;/login&#34;</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;username&#34;</span><span class="p">:</span> <span class="s2">&#34;alexjacobs&#34;</span><span class="p">,</span> <span class="s2">&#34;password&#34;</span><span class="p">:</span> <span class="s2">&#34;secret&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln"> 4</span><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="mi">200</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">assert</span> <span class="s2">&#34;jwt&#34;</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">test_login_fail</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&#34;/login&#34;</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;username&#34;</span><span class="p">:</span> <span class="s2">&#34;alexjacobs&#34;</span><span class="p">,</span> <span class="s2">&#34;password&#34;</span><span class="p">:</span> <span class="s2">&#34;not_the_right_password&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">10</span><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="mi">401</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span> <span class="o">==</span> <span class="p">{</span><span class="s1">&#39;detail&#39;</span><span class="p">:</span> <span class="s1">&#39;Incorrect username or password&#39;</span><span class="p">}</span>
</span></span></code></pre></div><p>These initial tests don&rsquo;t require mocking the authentication process because they are designed to test the
endpoint&rsquo;s basic functionality. They act as a foundational layer upon which we will build more complex test scenarios
that require mocking.</p>
<h3 id="mocking-authentication-in-our-test-client">Mocking Authentication in Our Test Client</h3>
<p>Now, let&rsquo;s say we have another endpoint that requires authentication. We&rsquo;ll use the <code>Depends</code> function to inject our
authentication function into our endpoint. Our <code>/users/me</code> endpoint returns information about the user logged in.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: app/main.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@app</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/users/me/&#34;</span><span class="p">,</span> <span class="n">response_model</span><span class="o">=</span><span class="n">User</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">read_users_me</span><span class="p">(</span><span class="n">_user</span><span class="p">:</span> <span class="n">TokenData</span> <span class="o">=</span> <span class="n">Depends</span><span class="p">(</span><span class="n">get_auth</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">user</span> <span class="o">=</span> <span class="n">users_db</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">_user</span><span class="o">.</span><span class="n">username</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">if</span> <span class="n">user</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="k">raise</span> <span class="n">HTTPException</span><span class="p">(</span><span class="n">status_code</span><span class="o">=</span><span class="mi">404</span><span class="p">,</span> <span class="n">detail</span><span class="o">=</span><span class="s2">&#34;User not found&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">return</span> <span class="n">user</span>
</span></span></code></pre></div><p>Authentication is handled by the <code>get_auth</code> function, which is injected into our endpoint using the <code>Depends</code>
function.<br>
Since we don&rsquo;t want to use real authentication in our tests, we&rsquo;re going to mock this function. I&rsquo;m going to show
multiple ways of doing this so you can choose the one that works best for you.</p>
<p>The most standard way of mocking this function is to use the <code>dependency_overrides</code> feature of FastAPI, before we initialize our
TestClient.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># File: tests/conftest.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">client</span><span class="p">(</span><span class="n">mock_s3_bucket</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1"># we patch auth within our client fixture</span>
</span></span><span class="line"><span class="ln"> 5</span><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="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="nf">mock_get_auth</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">return</span> <span class="n">TokenData</span><span class="p">(</span><span class="n">username</span><span class="o">=</span><span class="s2">&#34;alexjacobs&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">app</span><span class="o">.</span><span class="n">dependency_overrides</span><span class="p">[</span><span class="n">get_auth</span><span class="p">]</span> <span class="o">=</span> <span class="n">mock_get_auth</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">with</span> <span class="n">TestClient</span><span class="p">(</span><span class="n">app</span><span class="p">)</span> <span class="k">as</span> <span class="n">test_client</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">yield</span> <span class="n">test_client</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">app</span><span class="o">.</span><span class="n">dependency_overrides</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
</span></span></code></pre></div><p>You&rsquo;ll see we declare a function called <code>mock_get_auth</code> that returns a <code>TokenData</code> object.  Then in line 10,
<code>app.dependency_overrides[get_auth] = mock_get_auth</code>, we &lsquo;inject&rsquo; our mock function into our app in place of the
<code>get_auth</code> function.</p>
<p>This code is essentially replacing the <code>get_auth</code> function in our app with a mock function that returns the TokenData
object hardcoded into the function we&rsquo;re pathing with.</p>
<p>Then, in our test we pass in the client fixture, and we can see that our test passes.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: tests/test_auth_api.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">test_get_me_patched_auth</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="c1"># auth works without real jwt because we patched it in the client fixture</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/users/me&#34;</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;Authorization&#34;</span><span class="p">:</span> <span class="s2">&#34;Bearer &#34;</span> <span class="o">+</span> <span class="s1">&#39;fake.jwt&#39;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">5</span><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="mi">200</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span> <span class="o">==</span> <span class="p">{</span><span class="s1">&#39;username&#39;</span><span class="p">:</span> <span class="s1">&#39;alexjacobs&#39;</span><span class="p">,</span> <span class="s1">&#39;email&#39;</span><span class="p">:</span> <span class="s1">&#39;alex@example.com&#39;</span><span class="p">}</span>
</span></span></code></pre></div><p>(we really don&rsquo;t even need to include auth headers, since we&rsquo;re mocking the auth function, but I&rsquo;m keeping it
here for clarity)</p>
<h3 id="mocking-authentication-directly-in-your-tests">Mocking Authentication Directly in your Tests</h3>
<p>Now, what if we want to mock the auth function in a different way? We can do this by mocking the auth function directly
in our test.</p>
<p>First, we&rsquo;ll create a second client fixture that doesn&rsquo;t patch the auth function.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: tests/conftest.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">client_unpatched_auth</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="c1"># we don&#39;t patch auth for this client</span>
</span></span><span class="line"><span class="ln">5</span><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="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">with</span> <span class="n">TestClient</span><span class="p">(</span><span class="n">app</span><span class="p">)</span> <span class="k">as</span> <span class="n">test_client_2</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">        <span class="k">yield</span> <span class="n">test_client_2</span>
</span></span></code></pre></div><p>Now we&rsquo;re going to write a test to verify that auth fails (since we&rsquo;re not patching the auth function)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: tests/test_auth_api.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">test_get_me_unpatched_auth</span><span class="p">(</span><span class="n">client_unpatched_auth</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="c1"># auth fails without real jwt because we&#39;re using the unpatched client fixture</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client_unpatched_auth</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/users/me&#34;</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;Authorization&#34;</span><span class="p">:</span> <span class="s2">&#34;Bearer &#34;</span> <span class="o">+</span> <span class="s1">&#39;fake.jwt&#39;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">5</span><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="mi">401</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span> <span class="o">==</span> <span class="p">{</span><span class="s2">&#34;detail&#34;</span><span class="p">:</span> <span class="s2">&#34;Could not validate credentials&#34;</span><span class="p">}</span>
</span></span></code></pre></div><p>To make this work, there are two approaches. The first is to mock the auth function directly in the test</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># File: tests/test_auth_api.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">test_get_me_path_auth_in_fn</span><span class="p">(</span><span class="n">client_unpatched_auth</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1"># auth works because we patch it in this test (not in the client fixture)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">mock_get_auth</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">return</span> <span class="n">TokenData</span><span class="p">(</span><span class="n">username</span><span class="o">=</span><span class="s2">&#34;alexjacobs&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">app</span><span class="o">.</span><span class="n">dependency_overrides</span><span class="p">[</span><span class="n">get_auth</span><span class="p">]</span> <span class="o">=</span> <span class="n">mock_get_auth</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client_unpatched_auth</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/users/me&#34;</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;Authorization&#34;</span><span class="p">:</span> <span class="s2">&#34;Bearer &#34;</span> <span class="o">+</span> <span class="s1">&#39;fake.jwt&#39;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">10</span><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="mi">200</span>
</span></span></code></pre></div><p>All we&rsquo;ve done here is moved the code that patches the auth function into the test itself. This could be useful if you
wanted to mock the auth function in some tests, but not others (but only wanted to have one client fixture).</p>
<p>Next, we&rsquo;ll do the same thing, but we&rsquo;re going to use a new fixture and factory function to create our mock auth function.
So first, we&rsquo;ll create a new fixture,</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: tests/conftest.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">mock_get_auth_factory</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">def</span> <span class="nf">mock_get_auth</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="k">return</span> <span class="n">TokenData</span><span class="p">(</span><span class="n">username</span><span class="o">=</span><span class="s2">&#34;alexjacobs&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">return</span> <span class="n">mock_get_auth</span>
</span></span></code></pre></div><p>and then we&rsquo;ll use this fixture in our test</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: tests/test_auth_api.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">test_get_me_patch_auth_with_fixture</span><span class="p">(</span><span class="n">client_unpatched_auth</span><span class="p">,</span> <span class="n">mock_get_auth_factory</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="c1"># auth works because we patch it with a fixture instead of in the client fixture</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">app</span><span class="o">.</span><span class="n">dependency_overrides</span><span class="p">[</span><span class="n">get_auth</span><span class="p">]</span> <span class="o">=</span> <span class="n">mock_get_auth_factory</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client_unpatched_auth</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/users/me&#34;</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;Authorization&#34;</span><span class="p">:</span> <span class="s2">&#34;Bearer &#34;</span> <span class="o">+</span> <span class="s1">&#39;fake.jwt&#39;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">6</span><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="mi">200</span>
</span></span></code></pre></div><p>Notice that when we&rsquo;re passing in the <code>mock_get_auth_factory</code> fixture, we&rsquo;re passing in the function itself, not the
return value of the function. This is because the fixture is a factory function that returns a function, so we need to
pass in the function itself.</p>
<p>This may not seem very useful (and for mocking auth, it may not be), but there are plenty of scenarios when you may
want to mock a function in some tests, but not others. This is a good way to do that.</p>
<h2 id="mocking-external-apis">Mocking External APIs</h2>
<p>Our FastAPI application makes external API calls to fetch weather data. During testing, we don&rsquo;t want to make real API
calls because they can be slow and unreliable. Therefore, we mock the external API calls.</p>
<p>We mock the external API calls by patching the <code>fetch_weather</code> function in our FastAPI application. This function is
responsible for making the actual API call and returning the weather data. We replace it with a mock function that
always returns a predefined weather data. But, this function does use dependency injection (the <code>Depends</code> funciton)
in our app, so we have to mock it in a different way.</p>
<p>First, let&rsquo;s look at our endpoint and the function it calls.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: app/main.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@app</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/weather/</span><span class="si">{city}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">response_model</span><span class="o">=</span><span class="n">WeatherResponse</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">get_weather</span><span class="p">(</span><span class="n">city</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">_user</span><span class="p">:</span> <span class="n">TokenData</span> <span class="o">=</span> <span class="n">Depends</span><span class="p">(</span><span class="n">get_auth</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">weather</span> <span class="o">=</span> <span class="n">fetch_weather</span><span class="p">(</span><span class="n">city</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">return</span> <span class="n">WeatherResponse</span><span class="p">(</span><span class="n">city</span><span class="o">=</span><span class="n">city</span><span class="p">,</span> <span class="n">weather</span><span class="o">=</span><span class="n">weather</span><span class="p">)</span>
</span></span></code></pre></div><p>and our <code>fetch_weather()</code> function</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: app/helpers.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">fetch_weather</span><span class="p">(</span><span class="n">city_name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;http://wttr.in/</span><span class="si">{</span><span class="n">city_name</span><span class="si">}</span><span class="s2">?format=%C+%t&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">text</span>
</span></span></code></pre></div><p>Pretty simple&hellip; we are hitting an external API, though, so we need to mock this out in our tests.</p>
<p>We have three ways to mock the external API calls:</p>
<h3 id="mocking-external-apis-with-unittestmockpatch-directly-in-our-test">Mocking External APIs with unittest.mock.patch Directly in Our Test</h3>
<p>Using unittest.mock.patch, we patch our <code>fetch_weather</code> function to return &lsquo;sunny&rsquo;.
We then verify our endpoint returns the expected response (and verify that our mock function was called&ndash;this last part
probably isn&rsquo;t really necessary here, but is a good demonstration)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: tests/test_weather_api.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">test_get_weather</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s1">&#39;app.main.fetch_weather&#39;</span><span class="p">,</span> <span class="n">return_value</span><span class="o">=</span><span class="s1">&#39;sunny&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock_fetch_weather</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/weather/atlanta&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><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="mi">200</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span> <span class="o">==</span> <span class="p">{</span><span class="s1">&#39;city&#39;</span><span class="p">:</span> <span class="s1">&#39;atlanta&#39;</span><span class="p">,</span> <span class="s1">&#39;weather&#39;</span><span class="p">:</span> <span class="s1">&#39;sunny&#39;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="n">mock_fetch_weather</span><span class="o">.</span><span class="n">assert_called_once_with</span><span class="p">(</span><span class="s1">&#39;atlanta&#39;</span><span class="p">)</span>
</span></span></code></pre></div><h3 id="creating-a-fixture-that-patches-the-function">Creating a fixture that patches the function</h3>
<p>Let&rsquo;s suggest that we may want to test multiple endpoints that call the <code>fetch_weather</code> function. We could patch the
function in each test, but that&rsquo;s a lot of code duplication. Instead, we can create a fixture that patches the
function. First, we&rsquo;ll set up our fixture (this is another factor function). We&rsquo;re using <code>unittest</code>&rsquo;s Mock and Patch
functions in order to replace the <code>fetch_weather</code> function with a mock function that returns &lsquo;rainy&rsquo;.</p>
<p>Note: As before, we&rsquo;re using a factory function to create our mock function, so we need to pass in the function itself,
not the return value of the function.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: tests/conftest.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">mock_fetch_weather_factory</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">def</span> <span class="nf">mock_fetch_weather</span><span class="p">(</span><span class="n">city</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;rainy&#34;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">(</span><span class="n">side_effect</span><span class="o">=</span><span class="n">mock_fetch_weather</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="k">with</span> <span class="n">patch</span><span class="o">.</span><span class="n">object</span><span class="p">(</span><span class="n">app</span><span class="o">.</span><span class="n">main</span><span class="p">,</span> <span class="s1">&#39;fetch_weather&#39;</span><span class="p">,</span> <span class="n">new</span><span class="o">=</span><span class="n">mock</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">        <span class="k">yield</span>
</span></span></code></pre></div><p>In our test, we just need to again pass in the fixture as an argument.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: tests/test_weather_api.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">test_get_weather1</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">mock_fetch_weather_factory</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/weather/atlanta&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><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="mi">200</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span> <span class="o">==</span> <span class="p">{</span><span class="s1">&#39;city&#39;</span><span class="p">:</span> <span class="s1">&#39;atlanta&#39;</span><span class="p">,</span> <span class="s1">&#39;weather&#39;</span><span class="p">:</span> <span class="s1">&#39;rainy&#39;</span><span class="p">}</span>
</span></span></code></pre></div><h3 id="creating-a-parametrized-fixture">Creating a parametrized fixture</h3>
<p>The above example probably isn&rsquo;t very useful in practice. In the real world, if we&rsquo;re testing multiple endpoints that
call the same function, we probably want to test different scenarios. For example, we may want to test that our
endpoint returns the correct response for different weather conditions. We could do this by creating multiple fixtures
that patch the function with different mock functions, but this is a lot of code duplication and basically defeats the
purpose of using a fixture at all. Instead, we can use a parametrized fixture to pass in values to our fixture to
make it behave differently depending on our test.</p>
<p>To do this, we&rsquo;re going to create another fixture, but this one will take a parameter. It essentially wraps our previous
factory function, but now we can pass in a parameter to the fixture.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># File: tests/conftest.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">mock_fetch_weather_parametrized</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">mock_fetch_weather_factory</span><span class="p">(</span><span class="n">weather</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">def</span> <span class="nf">mock_fetch_weather</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="ln"> 6</span><span class="cl">            <span class="k">return</span> <span class="n">weather</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">return</span> <span class="n">mock_fetch_weather</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">mock_weather</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">param</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">with</span> <span class="n">patch</span><span class="o">.</span><span class="n">object</span><span class="p">(</span><span class="n">app</span><span class="o">.</span><span class="n">main</span><span class="p">,</span> <span class="s1">&#39;fetch_weather&#39;</span><span class="p">,</span> <span class="n">new</span><span class="o">=</span><span class="n">mock_fetch_weather_factory</span><span class="p">(</span><span class="n">mock_weather</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">yield</span>
</span></span></code></pre></div><p>And then, when we call our test, we need to decorate it with <code>pytest.mark.parametrize</code>, and pass in the
fixture as an argument. I&rsquo;ve set up two tests here so we can really see how it works.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># File: tests/test_weather_api.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">&#39;mock_fetch_weather_parametrized&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s1">&#39;cloudy&#39;</span><span class="p">],</span> <span class="n">indirect</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">test_get_weather_parameterized</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">mock_fetch_weather_parametrized</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/weather/atlanta&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><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="mi">200</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span> <span class="o">==</span> <span class="p">{</span><span class="s1">&#39;city&#39;</span><span class="p">:</span> <span class="s1">&#39;atlanta&#39;</span><span class="p">,</span> <span class="s1">&#39;weather&#39;</span><span class="p">:</span> <span class="s1">&#39;cloudy&#39;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">&#39;mock_fetch_weather_parametrized&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s1">&#39;tornados&#39;</span><span class="p">],</span> <span class="n">indirect</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">test_get_weather_parameterized</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">mock_fetch_weather_parametrized</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/weather/atlanta&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><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="mi">200</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span> <span class="o">==</span> <span class="p">{</span><span class="s1">&#39;city&#39;</span><span class="p">:</span> <span class="s1">&#39;atlanta&#39;</span><span class="p">,</span> <span class="s1">&#39;weather&#39;</span><span class="p">:</span> <span class="s1">&#39;tornados&#39;</span><span class="p">}</span>
</span></span></code></pre></div><p>The parameterized fixture is more complicated to set up, but it is incredibly useful in practice when you have to
simulate different responses from external APIs.</p>
<h2 id="mocking-mongodb">Mocking MongoDB</h2>
<p>Our FastAPI application interacts with MongoDB. During testing, we might not want to (or really be able to in some cases)
hit a real database with real/mocked data.  Instead, we acn mock MongoDB by using the <a href="https://github.com/mongomock/mongomock">mongomock</a> library, which
simulates a MongoDB client. (Note: In my experience, mongomock can be difficult to work with and some practice to get
working, but were going to proceed with it for now)</p>
<h3 id="creating-a-mock-mongodb-client">Creating a Mock MongoDB Client</h3>
<p>In our application, we employ context management for database interactions, utilizing Python&rsquo;s <code>with</code> statement. This
demands that the object used within the <code>with</code> statement must implement context management protocols, specifically
<code>__enter__</code> and <code>__exit__</code> methods.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># File: app/db.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">get_mongodb</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">with</span> <span class="n">MongoClient</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="ln"> 6</span><span class="cl">            <span class="n">db</span> <span class="o">=</span> <span class="n">client</span><span class="p">[</span><span class="s2">&#34;preferences&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="k">yield</span> <span class="n">db</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;Error: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">raise</span>
</span></span></code></pre></div><p>The mongomock library doesn&rsquo;t implement these methods, so to align with these requirements, we define a custom
MockMongoClient class to wrap our mongomock client. This class mimics the behavior of the actual
MongoClient by implementing the <code>__enter__</code> and <code>__exit__</code> methods. This ensures compatibility with the existing code
that expects a context-managed database client.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># File: tests/conftest.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">MockMongoClient</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><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">db</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">db</span> <span class="o">=</span> <span class="n">db</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">def</span> <span class="fm">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">db</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="fm">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">pass</span>
</span></span></code></pre></div><p>Using our MockMongoClient class, we can now create our mock database client. We&rsquo;ll do this in a fixture so we can
reuse it in multiple tests.</p>
<h3 id="creating-a-mock-mongodb-client-fixtures">Creating a Mock MongoDB Client Fixtures</h3>
<p>I&rsquo;m going to initialize two separate fixtures here, one with an empty database and one with some data initialized. (In
theory, we should be able to set the scope of the fixture to <code>session</code> or <code>module</code> and just initialize the database
once, add data through other tests, and then use that data for testing later, but I haven&rsquo;t been able to get that
working with mongomock. The project seems to have been designed more with unit tests in mind and is not a complete
implementation or drop in replacement. If you know how to make this work, please let me know!)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: tests/conftest.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">mock_mongodb</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">def</span> <span class="nf">mock_get_mongodb</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="n">mock_client</span> <span class="o">=</span> <span class="n">MongoClient</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="k">return</span> <span class="n">MockMongoClient</span><span class="p">(</span><span class="n">mock_client</span><span class="o">.</span><span class="n">db</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="k">return</span> <span class="n">mock_get_mongodb</span>
</span></span></code></pre></div><p>and initialize some data&hellip;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: tests/conftest.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">mock_mongodb_initialized</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">def</span> <span class="nf">mock_get_mongodb</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="n">mock_client</span> <span class="o">=</span> <span class="n">MongoClient</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="n">mock_client</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">preferences</span><span class="o">.</span><span class="n">insert_one</span><span class="p">({</span><span class="s2">&#34;username&#34;</span><span class="p">:</span> <span class="s2">&#34;alexjacobs&#34;</span><span class="p">,</span> <span class="s2">&#34;city&#34;</span><span class="p">:</span> <span class="s2">&#34;Berlin&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="k">return</span> <span class="n">MockMongoClient</span><span class="p">(</span><span class="n">mock_client</span><span class="o">.</span><span class="n">db</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="k">return</span> <span class="n">mock_get_mongodb</span>
</span></span></code></pre></div><p>And now we can write our tests. We&rsquo;ll pass in our fixture and again use the <code>dependency_overrides</code> feature to inject
our
mock database client into our app (overriding the <code>get_mongodb</code> function)</p>
<h3 id="writing-tests-integration-tests-using-our-mocked-mongodb-client">Writing Tests Integration Tests Using Our Mocked MongoDB Client</h3>
<p>We&rsquo;ve got three simple tests here.<br>
First, we test that our endpoint correctly returns a 404 if no user preferences exist (we use our non-initialized
<code>mock_mongodb</code> fixture for this)</p>
<p>Next, we use our initialized <code>mock_mongodb</code> fixture to test that our endpoint correctly returns the user preferences.</p>
<p>Finally, we test that we can save user preferences (we use our non-initialized <code>mock_mongodb</code> fixture for this, but it
should work with either)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># File: tests/user_preference_api.py</span>
</span></span><span class="line"><span class="ln"> 2</span><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="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">app.fake_db</span> <span class="kn">import</span> <span class="n">get_mongodb</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">test_get_user_preferences_404</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">mock_mongodb</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># should return 404 since no user preferences exist</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">app</span><span class="o">.</span><span class="n">dependency_overrides</span><span class="p">[</span><span class="n">get_mongodb</span><span class="p">]</span> <span class="o">=</span> <span class="n">mock_mongodb</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/users/me/preferences&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><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="mi">404</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span> <span class="o">==</span> <span class="p">{</span><span class="s1">&#39;detail&#39;</span><span class="p">:</span> <span class="s1">&#39;Preferences not found&#39;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">def</span> <span class="nf">test_get_user_preferences_initialized</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">mock_mongodb_initialized</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># we&#39;re using our mock_mongodb_initialized fixture here which has our user preferences already set</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">app</span><span class="o">.</span><span class="n">dependency_overrides</span><span class="p">[</span><span class="n">get_mongodb</span><span class="p">]</span> <span class="o">=</span> <span class="n">mock_mongodb_initialized</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/users/me/preferences&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><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="mi">200</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span> <span class="o">==</span> <span class="p">{</span><span class="s1">&#39;city&#39;</span><span class="p">:</span> <span class="s1">&#39;Berlin&#39;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">def</span> <span class="nf">test_post_user_preferences</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">mock_mongodb</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">app</span><span class="o">.</span><span class="n">dependency_overrides</span><span class="p">[</span><span class="n">get_mongodb</span><span class="p">]</span> <span class="o">=</span> <span class="n">mock_mongodb</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&#34;/users/me/preferences&#34;</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;city&#34;</span><span class="p">:</span> <span class="s2">&#34;Berlin&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">25</span><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="mi">200</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span> <span class="o">==</span> <span class="p">{</span><span class="s2">&#34;detail&#34;</span><span class="p">:</span> <span class="s2">&#34;success&#34;</span><span class="p">}</span>
</span></span></code></pre></div><p>And that&rsquo;s it!  Pretty simple to write the actual tests once we have our mocked database client set up correctly.</p>
<h2 id="mocking-aws-s3">Mocking AWS S3</h2>
<p>Our FastAPI application interacts with AWS S3 for storing and retrieving user profile pictures. During testing, we don&rsquo;t
want to use a real S3 bucket because it would require us to manage real AWS resources. This would complicate our tests
and potentially incur costs. Therefore, we mock the S3 bucket. (The same patterns used here could be applied to other
AWS services)</p>
<h3 id="creating-the-mock-s3-fixture">Creating the Mock S3 Fixture</h3>
<p>We mock the S3 bucket by using the mock_s3 decorator from the moto library, which simulates an S3 bucket. This is done
in a fixture so we can reuse it in multiple tests.</p>
<p>(moto is a great library for mocking AWS services, and you&rsquo;ll see we are able to set our fixture scope to session,
allowing us to maintain bucket state across multiple tests)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># File: tests/conftest.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nd">@pytest</span><span class="o">.</span><span class="n">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="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">mock_s3_bucket</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">with</span> <span class="n">mock_s3</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="n">conn</span> <span class="o">=</span> <span class="n">boto3</span><span class="o">.</span><span class="n">resource</span><span class="p">(</span><span class="s1">&#39;s3&#39;</span><span class="p">,</span> <span class="n">region_name</span><span class="o">=</span><span class="s1">&#39;us-east-1&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="n">conn</span><span class="o">.</span><span class="n">create_bucket</span><span class="p">(</span><span class="n">Bucket</span><span class="o">=</span><span class="n">bucket</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="c1"># we could upload a test file(s) here if we wanted to</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="c1"># s3_client = boto3.client(&#39;s3&#39;, region_name=&#39;us-east-1&#39;)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="c1"># s3_client.upload_file(&#39;tests/assets/duck.png&#39;, bucket, &#39;profile_pics/alexjacobs.png&#39;) </span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">yield</span>
</span></span></code></pre></div><h3 id="integrating-with-test-client">Integrating with Test Client</h3>
<p>This looks similar to our MockMongo fixtures, but this time, rather than pass our fixture into our tests, we pass it
into the client fixture.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># File: tests/conftest.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">client</span><span class="p">(</span><span class="n">mock_s3_bucket</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1"># we patch auth within our client fixture</span>
</span></span><span class="line"><span class="ln"> 5</span><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="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="nf">mock_get_auth</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">return</span> <span class="n">TokenData</span><span class="p">(</span><span class="n">username</span><span class="o">=</span><span class="s2">&#34;alexjacobs&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">app</span><span class="o">.</span><span class="n">dependency_overrides</span><span class="p">[</span><span class="n">get_auth</span><span class="p">]</span> <span class="o">=</span> <span class="n">mock_get_auth</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">with</span> <span class="n">TestClient</span><span class="p">(</span><span class="n">app</span><span class="p">)</span> <span class="k">as</span> <span class="n">test_client</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">yield</span> <span class="n">test_client</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">app</span><span class="o">.</span><span class="n">dependency_overrides</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
</span></span></code></pre></div><h3 id="writing-the-tests">Writing the Tests</h3>
<p>Now we can write our tests. We&rsquo;ll start by testing that we get the right message if no profile picture exists for the
user</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: tests / test_user_preference_api.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">test_get_user_profile_pic_404</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/users/me/profile_pic&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><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="mi">404</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span> <span class="o">==</span> <span class="p">{</span><span class="s1">&#39;detail&#39;</span><span class="p">:</span> <span class="s1">&#39;No profile pic found&#39;</span><span class="p">}</span>
</span></span></code></pre></div><p>Now we&rsquo;ll test adding one&hellip;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: tests/test_user_preference_api.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">test_set_user_profile_pic</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s1">&#39;tests/assets/duck.png&#39;</span><span class="p">,</span> <span class="s1">&#39;rb&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&#34;/users/me/profile_pic&#34;</span><span class="p">,</span> <span class="n">files</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;picture&#34;</span><span class="p">:</span> <span class="p">(</span><span class="s2">&#34;duck.png&#34;</span><span class="p">,</span> <span class="n">f</span><span class="p">,</span> <span class="s2">&#34;image/png&#34;</span><span class="p">)})</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><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="mi">200</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span> <span class="o">==</span> <span class="p">{</span><span class="s1">&#39;detail&#39;</span><span class="p">:</span> <span class="s1">&#39;success&#39;</span><span class="p">}</span>
</span></span></code></pre></div><p>Finally, we can test getting one. (notice how this is using the same file we uploaded in the previous test, this is due
to the fact that we&rsquo;re using the session scope for our mock_s3_bucket fixture)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># File: tests/test_user_preference_api.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">test_get_user_profile_pic</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/users/me/profile_pic&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><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="mi">200</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s1">&#39;tests/assets/duck.png&#39;</span><span class="p">,</span> <span class="s1">&#39;rb&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="n">original_image_data</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="n">original_base64</span> <span class="o">=</span> <span class="n">base64</span><span class="o">.</span><span class="n">b64encode</span><span class="p">(</span><span class="n">original_image_data</span><span class="p">)</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()[</span><span class="s1">&#39;image&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="n">original_base64</span>
</span></span></code></pre></div><h2 id="wrapping-up">Wrapping Up</h2>
<p>This was a fairly deep dive.  We&rsquo;ve unraveled the intricacies of integration testing in FastAPI, which is not without
its challenges when it comes to mocking external dependencies. We&rsquo;ve gone through a
variety of techniques to mock authentication, from simple dependency_overrides to more advanced fixture-based
strategies. We&rsquo;ve also tackled how to mock external APIs using Python&rsquo;s unittest.mock.patch and pytest&rsquo;s parametrized
fixtures.</p>
<p>When it comes to databases, MongoDB adds another layer of complexity. We&rsquo;ve seen how Mongomock can be a useful tool,
albeit with its own set of limitations. We crafted custom mock MongoDB clients and fixtures to ease this pain point. As
for AWS S3, the Moto library proved to be a robust tool, enabling us to mock S3 buckets effectively, even allowing state
persistence across tests.</p>
<p>The aim has been to arm you with a set of tools and strategies for your FastAPI testing arsenal. Whether it&rsquo;s a
simple authentication mock or a more complex external service, you should now be equipped to tackle these head-on. Happy
testing.</p>
<p>Happy testing.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
