mirror of
https://github.com/kennethreitz/responder.git
synced 2026-06-05 14:50:19 +00:00
435 lines
45 KiB
HTML
435 lines
45 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<html lang="en" data-content_root="./">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
|
||
<title>Testing — responder 3.6.2 documentation</title>
|
||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=5ecbeea2" />
|
||
<link rel="stylesheet" type="text/css" href="_static/basic.css?v=b08954a9" />
|
||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=27fed22d" />
|
||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||
<link rel="stylesheet" type="text/css" href="_static/design-elements.e5416f61bae5d36adc6d722a2b6f8cff.css?v=452a8e97" />
|
||
<script src="_static/documentation_options.js?v=c0c9fa11"></script>
|
||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||
<script src="_static/copybutton.js?v=fd10adb8"></script>
|
||
<script>
|
||
</script>
|
||
<script src="_static/design-elements.bbdccc18c4abea9397628f9fea3d48c2.js?v=03c7770e"></script>
|
||
<link rel="index" title="Index" href="genindex.html" />
|
||
<link rel="search" title="Search" href="search.html" />
|
||
<link rel="next" title="API Reference" href="api.html" />
|
||
<link rel="prev" title="Deployment" href="deployment.html" />
|
||
|
||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
||
|
||
|
||
|
||
|
||
|
||
</head><body>
|
||
|
||
|
||
<div class="document">
|
||
<div class="documentwrapper">
|
||
<div class="bodywrapper">
|
||
|
||
|
||
<div class="body" role="main">
|
||
|
||
<section id="testing">
|
||
<h1>Testing<a class="headerlink" href="#testing" title="Link to this heading">¶</a></h1>
|
||
<p>Responder includes a built-in test client powered by Starlette’s
|
||
<code class="docutils literal notranslate"><span class="pre">TestClient</span></code>. You don’t need to start a server — tests run in-process,
|
||
making them fast and reliable. There’s no separate test server to manage,
|
||
no ports to allocate, and no race conditions to worry about. Just import
|
||
your app and start making requests.</p>
|
||
<section id="getting-started">
|
||
<h2>Getting Started<a class="headerlink" href="#getting-started" title="Link to this heading">¶</a></h2>
|
||
<p>Given a simple application in <code class="docutils literal notranslate"><span class="pre">api.py</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">responder</span>
|
||
|
||
<span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">()</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">hello</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s2">"hello, world!"</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
|
||
<span class="n">api</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>You can test it with pytest. Every Responder <code class="docutils literal notranslate"><span class="pre">API</span></code> instance has a
|
||
<code class="docutils literal notranslate"><span class="pre">requests</span></code> property that gives you a test client — use it exactly like
|
||
you’d use <code class="docutils literal notranslate"><span class="pre">requests</span></code> or <code class="docutils literal notranslate"><span class="pre">httpx</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># test_api.py</span>
|
||
<span class="kn">import</span><span class="w"> </span><span class="nn">api</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="nn">service</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">test_hello</span><span class="p">():</span>
|
||
<span class="n">r</span> <span class="o">=</span> <span class="n">service</span><span class="o">.</span><span class="n">api</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="s2">"/"</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">text</span> <span class="o">==</span> <span class="s2">"hello, world!"</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Run your tests:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ pytest
|
||
</pre></div>
|
||
</div>
|
||
<p>That’s really all there is to it. No configuration, no test server setup.</p>
|
||
</section>
|
||
<section id="using-fixtures">
|
||
<h2>Using Fixtures<a class="headerlink" href="#using-fixtures" title="Link to this heading">¶</a></h2>
|
||
<p>For larger test suites, pytest fixtures keep things organized. Create a
|
||
fixture that returns your API instance, and every test gets a fresh
|
||
reference to it:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||
<span class="kn">import</span><span class="w"> </span><span class="nn">api</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="nn">service</span>
|
||
|
||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">api</span><span class="p">():</span>
|
||
<span class="k">return</span> <span class="n">service</span><span class="o">.</span><span class="n">api</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">test_hello</span><span class="p">(</span><span class="n">api</span><span class="p">):</span>
|
||
<span class="n">r</span> <span class="o">=</span> <span class="n">api</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="s2">"/"</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">text</span> <span class="o">==</span> <span class="s2">"hello, world!"</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">test_json</span><span class="p">(</span><span class="n">api</span><span class="p">):</span>
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/data"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">data</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"key"</span><span class="p">:</span> <span class="s2">"value"</span><span class="p">}</span>
|
||
|
||
<span class="n">r</span> <span class="o">=</span> <span class="n">api</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">api</span><span class="o">.</span><span class="n">url_for</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
|
||
<span class="k">assert</span> <span class="n">r</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">"key"</span><span class="p">:</span> <span class="s2">"value"</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">api.url_for()</span></code> method generates a URL for a given route endpoint,
|
||
so you don’t have to hard-code paths in your tests. If you rename a route
|
||
later, your tests won’t break.</p>
|
||
</section>
|
||
<section id="testing-json-apis">
|
||
<h2>Testing JSON APIs<a class="headerlink" href="#testing-json-apis" title="Link to this heading">¶</a></h2>
|
||
<p>Most APIs send and receive JSON. The test client makes this natural — pass
|
||
<code class="docutils literal notranslate"><span class="pre">json=</span></code> to send a JSON body, and call <code class="docutils literal notranslate"><span class="pre">.json()</span></code> on the response to
|
||
parse it:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_create_item</span><span class="p">(</span><span class="n">api</span><span class="p">):</span>
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/items"</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">create</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">()</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"created"</span><span class="p">:</span> <span class="n">data</span><span class="p">}</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">201</span>
|
||
|
||
<span class="n">r</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">api</span><span class="o">.</span><span class="n">url_for</span><span class="p">(</span><span class="n">create</span><span class="p">),</span> <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s2">"name"</span><span class="p">:</span> <span class="s2">"widget"</span><span class="p">})</span>
|
||
<span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">201</span>
|
||
<span class="k">assert</span> <span class="n">r</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">"created"</span><span class="p">:</span> <span class="p">{</span><span class="s2">"name"</span><span class="p">:</span> <span class="s2">"widget"</span><span class="p">}}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>You can also test content negotiation by setting the <code class="docutils literal notranslate"><span class="pre">Accept</span></code> header:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">r</span> <span class="o">=</span> <span class="n">api</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="s2">"/data"</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s2">"Accept"</span><span class="p">:</span> <span class="s2">"application/x-yaml"</span><span class="p">})</span>
|
||
<span class="k">assert</span> <span class="s2">"key: value"</span> <span class="ow">in</span> <span class="n">r</span><span class="o">.</span><span class="n">text</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="testing-request-validation">
|
||
<h2>Testing Request Validation<a class="headerlink" href="#testing-request-validation" title="Link to this heading">¶</a></h2>
|
||
<p>If you’re using Pydantic models for request validation, you can test
|
||
that invalid inputs are properly rejected:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">pydantic</span><span class="w"> </span><span class="kn">import</span> <span class="n">BaseModel</span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">Item</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
|
||
<span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
|
||
<span class="n">price</span><span class="p">:</span> <span class="nb">float</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">test_validation</span><span class="p">(</span><span class="n">api</span><span class="p">):</span>
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/items"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">"POST"</span><span class="p">],</span> <span class="n">request_model</span><span class="o">=</span><span class="n">Item</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">create</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">()</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="n">data</span>
|
||
|
||
<span class="c1"># Valid request</span>
|
||
<span class="n">r</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">"/items"</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s2">"name"</span><span class="p">:</span> <span class="s2">"thing"</span><span class="p">,</span> <span class="s2">"price"</span><span class="p">:</span> <span class="mf">9.99</span><span class="p">})</span>
|
||
<span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||
|
||
<span class="c1"># Missing required field</span>
|
||
<span class="n">r</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">"/items"</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s2">"name"</span><span class="p">:</span> <span class="s2">"thing"</span><span class="p">})</span>
|
||
<span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">422</span>
|
||
<span class="k">assert</span> <span class="s2">"errors"</span> <span class="ow">in</span> <span class="n">r</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="testing-file-uploads">
|
||
<h2>Testing File Uploads<a class="headerlink" href="#testing-file-uploads" title="Link to this heading">¶</a></h2>
|
||
<p>File uploads use the <code class="docutils literal notranslate"><span class="pre">files</span></code> parameter, just like the <code class="docutils literal notranslate"><span class="pre">requests</span></code>
|
||
library. Each file is a tuple of <code class="docutils literal notranslate"><span class="pre">(filename,</span> <span class="pre">content,</span> <span class="pre">content_type)</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_upload</span><span class="p">(</span><span class="n">api</span><span class="p">):</span>
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/upload"</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">upload</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">files</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">(</span><span class="s2">"files"</span><span class="p">)</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"received"</span><span class="p">:</span> <span class="nb">list</span><span class="p">(</span><span class="n">files</span><span class="o">.</span><span class="n">keys</span><span class="p">())}</span>
|
||
|
||
<span class="n">files</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"doc"</span><span class="p">:</span> <span class="p">(</span><span class="s2">"report.pdf"</span><span class="p">,</span> <span class="sa">b</span><span class="s2">"content"</span><span class="p">,</span> <span class="s2">"application/pdf"</span><span class="p">)}</span>
|
||
<span class="n">r</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">api</span><span class="o">.</span><span class="n">url_for</span><span class="p">(</span><span class="n">upload</span><span class="p">),</span> <span class="n">files</span><span class="o">=</span><span class="n">files</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="n">r</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">"received"</span><span class="p">:</span> <span class="p">[</span><span class="s2">"doc"</span><span class="p">]}</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="testing-headers-and-cookies">
|
||
<h2>Testing Headers and Cookies<a class="headerlink" href="#testing-headers-and-cookies" title="Link to this heading">¶</a></h2>
|
||
<p>Check response headers and cookies just like you would with any HTTP
|
||
client:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_headers</span><span class="p">(</span><span class="n">api</span><span class="p">):</span>
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">view</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-Custom"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"hello"</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">cookies</span><span class="p">[</span><span class="s2">"session"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"abc123"</span>
|
||
|
||
<span class="n">r</span> <span class="o">=</span> <span class="n">api</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="s2">"/"</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-Custom"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"hello"</span>
|
||
<span class="k">assert</span> <span class="s2">"session"</span> <span class="ow">in</span> <span class="n">r</span><span class="o">.</span><span class="n">cookies</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="testing-websockets">
|
||
<h2>Testing WebSockets<a class="headerlink" href="#testing-websockets" title="Link to this heading">¶</a></h2>
|
||
<p>WebSocket tests use Starlette’s <code class="docutils literal notranslate"><span class="pre">TestClient</span></code> directly, since WebSocket
|
||
connections require a different protocol. The <code class="docutils literal notranslate"><span class="pre">websocket_connect</span></code> context
|
||
manager gives you a connection you can send and receive on:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">starlette.testclient</span><span class="w"> </span><span class="kn">import</span> <span class="n">TestClient</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">test_websocket</span><span class="p">(</span><span class="n">api</span><span class="p">):</span>
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/ws"</span><span class="p">,</span> <span class="n">websocket</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">ws</span><span class="p">(</span><span class="n">ws</span><span class="p">):</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
|
||
<span class="n">name</span> <span class="o">=</span> <span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">receive_text</span><span class="p">()</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">send_text</span><span class="p">(</span><span class="sa">f</span><span class="s2">"hello, </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">!"</span><span class="p">)</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||
|
||
<span class="n">client</span> <span class="o">=</span> <span class="n">TestClient</span><span class="p">(</span><span class="n">api</span><span class="p">)</span>
|
||
<span class="k">with</span> <span class="n">client</span><span class="o">.</span><span class="n">websocket_connect</span><span class="p">(</span><span class="s2">"/ws"</span><span class="p">)</span> <span class="k">as</span> <span class="n">ws</span><span class="p">:</span>
|
||
<span class="n">ws</span><span class="o">.</span><span class="n">send_text</span><span class="p">(</span><span class="s2">"world"</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="n">ws</span><span class="o">.</span><span class="n">receive_text</span><span class="p">()</span> <span class="o">==</span> <span class="s2">"hello, world!"</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="testing-error-handling">
|
||
<h2>Testing Error Handling<a class="headerlink" href="#testing-error-handling" title="Link to this heading">¶</a></h2>
|
||
<p>By default, the test client raises exceptions from your route handlers,
|
||
which is usually what you want — it makes bugs obvious. But when you’re
|
||
testing error handling specifically, you want to see the error response
|
||
instead. Disable exception propagation with <code class="docutils literal notranslate"><span class="pre">raise_server_exceptions</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">starlette.testclient</span><span class="w"> </span><span class="kn">import</span> <span class="n">TestClient</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">test_500</span><span class="p">(</span><span class="n">api</span><span class="p">):</span>
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/fail"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">fail</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">"something broke"</span><span class="p">)</span>
|
||
|
||
<span class="n">client</span> <span class="o">=</span> <span class="n">TestClient</span><span class="p">(</span><span class="n">api</span><span class="p">,</span> <span class="n">raise_server_exceptions</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
|
||
<span class="n">r</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="n">api</span><span class="o">.</span><span class="n">url_for</span><span class="p">(</span><span class="n">fail</span><span class="p">))</span>
|
||
<span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">500</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>If you’ve registered a custom exception handler, you can test that too:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_custom_error</span><span class="p">(</span><span class="n">api</span><span class="p">):</span>
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">exception_handler</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">handle</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="n">exc</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">400</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"error"</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">exc</span><span class="p">)}</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/fail"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">fail</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">"bad input"</span><span class="p">)</span>
|
||
|
||
<span class="n">client</span> <span class="o">=</span> <span class="n">TestClient</span><span class="p">(</span><span class="n">api</span><span class="p">,</span> <span class="n">raise_server_exceptions</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
|
||
<span class="n">r</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="n">api</span><span class="o">.</span><span class="n">url_for</span><span class="p">(</span><span class="n">fail</span><span class="p">))</span>
|
||
<span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">400</span>
|
||
<span class="k">assert</span> <span class="n">r</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">"error"</span><span class="p">:</span> <span class="s2">"bad input"</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="testing-lifespan-events">
|
||
<h2>Testing Lifespan Events<a class="headerlink" href="#testing-lifespan-events" title="Link to this heading">¶</a></h2>
|
||
<p>If your app uses startup and shutdown events (for database connections,
|
||
caches, etc.), you need the test client to trigger them. Wrap the client
|
||
in a <code class="docutils literal notranslate"><span class="pre">with</span></code> block — startup runs on enter, shutdown runs on exit:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_with_lifespan</span><span class="p">(</span><span class="n">api</span><span class="p">):</span>
|
||
<span class="n">started</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"value"</span><span class="p">:</span> <span class="kc">False</span><span class="p">}</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">on_event</span><span class="p">(</span><span class="s2">"startup"</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">on_startup</span><span class="p">():</span>
|
||
<span class="n">started</span><span class="p">[</span><span class="s2">"value"</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">check</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"started"</span><span class="p">:</span> <span class="n">started</span><span class="p">[</span><span class="s2">"value"</span><span class="p">]}</span>
|
||
|
||
<span class="k">with</span> <span class="n">api</span><span class="o">.</span><span class="n">requests</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
|
||
<span class="n">r</span> <span class="o">=</span> <span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"http://;/"</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="n">r</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">"started"</span><span class="p">:</span> <span class="kc">True</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Without the <code class="docutils literal notranslate"><span class="pre">with</span></code> block, lifespan events won’t fire, which can lead to
|
||
confusing test failures if your routes depend on startup initialization.</p>
|
||
</section>
|
||
<section id="testing-before-and-after-hooks">
|
||
<h2>Testing Before and After Hooks<a class="headerlink" href="#testing-before-and-after-hooks" title="Link to this heading">¶</a></h2>
|
||
<p>Before-request and after-request hooks run automatically during tests,
|
||
just like in production. You can verify their effects on the response:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_hooks</span><span class="p">(</span><span class="n">api</span><span class="p">):</span>
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="n">before_request</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">add_version</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-Version"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"3.2"</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">after_request</span><span class="p">()</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">add_timing</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-Served-By"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"responder"</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">view</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s2">"ok"</span>
|
||
|
||
<span class="n">r</span> <span class="o">=</span> <span class="n">api</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="s2">"/"</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-Version"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"3.2"</span>
|
||
<span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-Served-By"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"responder"</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="testing-rate-limiting">
|
||
<h2>Testing Rate Limiting<a class="headerlink" href="#testing-rate-limiting" title="Link to this heading">¶</a></h2>
|
||
<p>Rate limiters are just hooks — they run automatically during tests.
|
||
Verify the headers and the 429 response:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.ratelimit</span><span class="w"> </span><span class="kn">import</span> <span class="n">RateLimiter</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">test_rate_limiting</span><span class="p">():</span>
|
||
<span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">allowed_hosts</span><span class="o">=</span><span class="p">[</span><span class="s2">"localhost"</span><span class="p">])</span>
|
||
<span class="n">limiter</span> <span class="o">=</span> <span class="n">RateLimiter</span><span class="p">(</span><span class="n">requests</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">period</span><span class="o">=</span><span class="mi">60</span><span class="p">)</span>
|
||
<span class="n">limiter</span><span class="o">.</span><span class="n">install</span><span class="p">(</span><span class="n">api</span><span class="p">)</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">view</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s2">"ok"</span>
|
||
|
||
<span class="c1"># First two requests succeed</span>
|
||
<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">2</span><span class="p">):</span>
|
||
<span class="n">r</span> <span class="o">=</span> <span class="n">api</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="s2">"http://localhost/"</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||
<span class="k">assert</span> <span class="s2">"X-RateLimit-Remaining"</span> <span class="ow">in</span> <span class="n">r</span><span class="o">.</span><span class="n">headers</span>
|
||
|
||
<span class="c1"># Third request is rate limited</span>
|
||
<span class="n">r</span> <span class="o">=</span> <span class="n">api</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="s2">"http://localhost/"</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">429</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="testing-mounted-apps">
|
||
<h2>Testing Mounted Apps<a class="headerlink" href="#testing-mounted-apps" title="Link to this heading">¶</a></h2>
|
||
<p>When testing WSGI apps mounted at a subroute, use <code class="docutils literal notranslate"><span class="pre">localhost</span></code> as the
|
||
host to avoid Werkzeug’s trusted host validation:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="n">Flask</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">test_flask_mount</span><span class="p">():</span>
|
||
<span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">allowed_hosts</span><span class="o">=</span><span class="p">[</span><span class="s2">"localhost"</span><span class="p">])</span>
|
||
|
||
<span class="n">flask_app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
|
||
<span class="nd">@flask_app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">hello</span><span class="p">():</span>
|
||
<span class="k">return</span> <span class="s2">"Hello from Flask!"</span>
|
||
|
||
<span class="n">api</span><span class="o">.</span><span class="n">mount</span><span class="p">(</span><span class="s2">"/flask"</span><span class="p">,</span> <span class="n">flask_app</span><span class="p">)</span>
|
||
|
||
<span class="n">r</span> <span class="o">=</span> <span class="n">api</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="s2">"http://localhost/flask"</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||
<span class="k">assert</span> <span class="s2">"Hello from Flask"</span> <span class="ow">in</span> <span class="n">r</span><span class="o">.</span><span class="n">text</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="tips">
|
||
<h2>Tips<a class="headerlink" href="#tips" title="Link to this heading">¶</a></h2>
|
||
<ul class="simple">
|
||
<li><p><strong>Keep tests fast.</strong> The in-process test client is already fast — no
|
||
network overhead. Avoid <code class="docutils literal notranslate"><span class="pre">time.sleep()</span></code> in tests.</p></li>
|
||
<li><p><strong>One API per test</strong> when testing configuration. If you need a specific
|
||
<code class="docutils literal notranslate"><span class="pre">API()</span></code> configuration (like <code class="docutils literal notranslate"><span class="pre">cors=True</span></code>), create a new instance
|
||
in the test rather than sharing the fixture.</p></li>
|
||
<li><p>Use <code class="docutils literal notranslate"><span class="pre">api.url_for()</span></code> instead of hard-coded paths. It’s a small
|
||
thing, but it makes refactoring painless.</p></li>
|
||
<li><p><strong>Test the contract, not the implementation.</strong> Assert on status codes,
|
||
response bodies, and headers — not on internal state.</p></li>
|
||
<li><p><strong>Use ``localhost`` for mounted WSGI apps.</strong> Werkzeug 3.1.7+ validates
|
||
the <code class="docutils literal notranslate"><span class="pre">Host</span></code> header, so avoid synthetic hosts like <code class="docutils literal notranslate"><span class="pre">;</span></code> in tests.</p></li>
|
||
</ul>
|
||
</section>
|
||
</section>
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="sphinxsidebar" role="navigation" aria-label="Main">
|
||
<div class="sphinxsidebarwrapper"><p class="logo">
|
||
<a href="index.html">
|
||
<img class="logo" src="_static/responder.png" />
|
||
</a>
|
||
</p>
|
||
<p>
|
||
<strong>Responder</strong> — a familiar HTTP service framework for Python.
|
||
<br />
|
||
<small>v3.6.2</small>
|
||
</p>
|
||
<h3>Useful Links</h3>
|
||
<ul>
|
||
<li><a href="https://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
|
||
<li><a href="https://pypi.org/project/responder/">Responder @ PyPI</a></li>
|
||
<li><a href="https://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
||
</ul>
|
||
<div>
|
||
<h3><a href="index.html">Table of Contents</a></h3>
|
||
<ul>
|
||
<li><a class="reference internal" href="#">Testing</a><ul>
|
||
<li><a class="reference internal" href="#getting-started">Getting Started</a></li>
|
||
<li><a class="reference internal" href="#using-fixtures">Using Fixtures</a></li>
|
||
<li><a class="reference internal" href="#testing-json-apis">Testing JSON APIs</a></li>
|
||
<li><a class="reference internal" href="#testing-request-validation">Testing Request Validation</a></li>
|
||
<li><a class="reference internal" href="#testing-file-uploads">Testing File Uploads</a></li>
|
||
<li><a class="reference internal" href="#testing-headers-and-cookies">Testing Headers and Cookies</a></li>
|
||
<li><a class="reference internal" href="#testing-websockets">Testing WebSockets</a></li>
|
||
<li><a class="reference internal" href="#testing-error-handling">Testing Error Handling</a></li>
|
||
<li><a class="reference internal" href="#testing-lifespan-events">Testing Lifespan Events</a></li>
|
||
<li><a class="reference internal" href="#testing-before-and-after-hooks">Testing Before and After Hooks</a></li>
|
||
<li><a class="reference internal" href="#testing-rate-limiting">Testing Rate Limiting</a></li>
|
||
<li><a class="reference internal" href="#testing-mounted-apps">Testing Mounted Apps</a></li>
|
||
<li><a class="reference internal" href="#tips">Tips</a></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
|
||
</div>
|
||
<search id="searchbox" style="display: none" role="search">
|
||
<h3 id="searchlabel">Quick search</h3>
|
||
<div class="searchformwrapper">
|
||
<form class="search" action="search.html" method="get">
|
||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
||
<input type="submit" value="Go" />
|
||
</form>
|
||
</div>
|
||
</search>
|
||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
||
</div>
|
||
</div>
|
||
<div class="clearer"></div>
|
||
</div>
|
||
<div class="footer">
|
||
©2018-2026, Kenneth Reitz.
|
||
|
||
|
|
||
<a href="_sources/testing.rst.txt"
|
||
rel="nofollow">Page source</a>
|
||
</div>
|
||
|
||
|
||
|
||
|
||
</body>
|
||
</html> |