<?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>Testing on Alex Jacobs</title>
    <link>https://alex-jacobs.com/tags/testing/</link>
    <description>Recent content in Testing on Alex Jacobs</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 11 Jun 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://alex-jacobs.com/tags/testing/index.xml" rel="self" type="application/rss+xml" />
    <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>
