mirror of
https://github.com/kennethreitz/responder.git
synced 2026-10-24 05:59:12 +00:00
712 lines
83 KiB
HTML
712 lines
83 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 9.2.0 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=eb4102c5" />
|
||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||
<link rel="stylesheet" type="text/css" href="_static/sphinx-design.min.css?v=95c83b7e" />
|
||
<link rel="stylesheet" type="text/css" href="_static/design-elements.e5416f61bae5d36adc6d722a2b6f8cff.css?v=452a8e97" />
|
||
<script src="_static/documentation_options.js?v=e8b940be"></script>
|
||
<script src="_static/doctools.js?v=fd6eb6e6"></script>
|
||
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
|
||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||
<script src="_static/copybutton.js?v=fd10adb8"></script>
|
||
<script>
|
||
</script>
|
||
<script src="_static/design-tabs.js?v=f930bc37"></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="Building a REST API" href="tutorial-rest.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. You can also give a route an explicit name and
|
||
reverse it by string — handy when the endpoint is a lambda or shares a name:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/users/</span><span class="si">{id}</span><span class="s2">"</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s2">"user_detail"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">user_detail</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="o">*</span><span class="p">,</span> <span class="nb">id</span><span class="p">):</span>
|
||
<span class="o">...</span>
|
||
|
||
<span class="n">api</span><span class="o">.</span><span class="n">url_for</span><span class="p">(</span><span class="s2">"user_detail"</span><span class="p">,</span> <span class="nb">id</span><span class="o">=</span><span class="mi">42</span><span class="p">)</span> <span class="c1"># -> "/users/42"</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="overriding-dependencies">
|
||
<h2>Overriding Dependencies<a class="headerlink" href="#overriding-dependencies" title="Link to this heading">¶</a></h2>
|
||
<p>Swap a dependency for a fake inside a <code class="docutils literal notranslate"><span class="pre">with</span></code> block — the override is
|
||
restored on exit, and it replaces (and bypasses the cache of) even an
|
||
<code class="docutils literal notranslate"><span class="pre">app</span></code>-scoped dependency:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">dependency</span><span class="p">()</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">db</span><span class="p">():</span>
|
||
<span class="k">return</span> <span class="n">RealDatabase</span><span class="p">()</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">test_users</span><span class="p">(</span><span class="n">api</span><span class="p">):</span>
|
||
<span class="k">with</span> <span class="n">api</span><span class="o">.</span><span class="n">dependency_overrides</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="n">FakeDatabase</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">"/users"</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>
|
||
</pre></div>
|
||
</div>
|
||
<p>Each value may be a bare object (wrapped automatically) or a provider callable
|
||
— a callable receives sub-dependencies and the request just like a real one.</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/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>Responder validates typed handler inputs for you, returning <code class="docutils literal notranslate"><span class="pre">422</span></code> with a
|
||
<code class="docutils literal notranslate"><span class="pre">{"errors":</span> <span class="pre">[...]}</span></code> body when something doesn’t fit. That makes validation
|
||
tests delightfully boring: send bad data, assert the status code.</p>
|
||
<p>The simplest form is a Pydantic-annotated body parameter. On <code class="docutils literal notranslate"><span class="pre">POST</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">PUT</span></code>, <code class="docutils literal notranslate"><span class="pre">PATCH</span></code>, and <code class="docutils literal notranslate"><span class="pre">DELETE</span></code> it receives the parsed, validated body —
|
||
no <code class="docutils literal notranslate"><span class="pre">await</span> <span class="pre">req.media()</span></code> required:</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_body_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="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="o">*</span><span class="p">,</span> <span class="n">item</span><span class="p">:</span> <span class="n">Item</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">item</span><span class="o">.</span><span class="n">model_dump</span><span class="p">()</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="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">"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="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>
|
||
<p>Query strings, headers, and cookies validate the same way, through the typed
|
||
parameter markers <code class="docutils literal notranslate"><span class="pre">Query</span></code>, <code class="docutils literal notranslate"><span class="pre">Header</span></code>, <code class="docutils literal notranslate"><span class="pre">Cookie</span></code>, and <code class="docutils literal notranslate"><span class="pre">Path</span></code> (see the
|
||
<a class="reference internal" href="tour.html"><span class="doc">tour</span></a>). A missing required value or a failed coercion is a
|
||
<code class="docutils literal notranslate"><span class="pre">422</span></code> too:</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</span><span class="w"> </span><span class="kn">import</span> <span class="n">Query</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">test_query_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">"/search"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">search</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="o">*</span><span class="p">,</span> <span class="n">q</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Query</span><span class="p">(</span><span class="o">...</span><span class="p">),</span> <span class="n">limit</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">Query</span><span class="p">(</span><span class="mi">10</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">"q"</span><span class="p">:</span> <span class="n">q</span><span class="p">,</span> <span class="s2">"limit"</span><span class="p">:</span> <span class="n">limit</span><span class="p">}</span>
|
||
|
||
<span class="c1"># limit is coerced from the query string to an int</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">"/search?q=hi&limit=5"</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">"q"</span><span class="p">:</span> <span class="s2">"hi"</span><span class="p">,</span> <span class="s2">"limit"</span><span class="p">:</span> <span class="mi">5</span><span class="p">}</span>
|
||
|
||
<span class="c1"># the required q is missing -> 422</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">"/search"</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-sessions">
|
||
<h2>Testing Sessions<a class="headerlink" href="#testing-sessions" title="Link to this heading">¶</a></h2>
|
||
<p>Sessions need a signing key. The old public default now raises, and an
|
||
instance built without one mints a random per-process key (fine for a quick
|
||
script, useless across a restart). So pass a real <code class="docutils literal notranslate"><span class="pre">secret_key</span></code>. Because the
|
||
test client speaks <code class="docutils literal notranslate"><span class="pre">http://</span></code>, also pass <code class="docutils literal notranslate"><span class="pre">session_https_only=False</span></code> —
|
||
otherwise the (Secure by default) session cookie won’t round-trip:</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_session</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">secret_key</span><span class="o">=</span><span class="s2">"test-secret-key-1234"</span><span class="p">,</span>
|
||
<span class="n">session_https_only</span><span class="o">=</span><span class="kc">False</span><span class="p">,</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">"/login"</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="k">def</span><span class="w"> </span><span class="nf">login</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">session</span><span class="p">[</span><span class="s2">"user"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"kenneth"</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/me"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">me</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">"user"</span><span class="p">:</span> <span class="n">req</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">"user"</span><span class="p">)}</span>
|
||
|
||
<span class="c1"># The test client persists cookies, so the session carries over.</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">"/login"</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">"/me"</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">"user"</span><span class="p">:</span> <span class="s2">"kenneth"</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">resp.session</span></code> is a write-through view of <code class="docutils literal notranslate"><span class="pre">req.session</span></code>, so assigning a
|
||
whole dict (<code class="docutils literal notranslate"><span class="pre">resp.session</span> <span class="pre">=</span> <span class="pre">{"user":</span> <span class="pre">"kenneth"}</span></code>) persists as well. See the
|
||
<a class="reference internal" href="guide-config.html"><span class="doc">configuration guide</span></a> for how the signing key is resolved
|
||
in production.</p>
|
||
</section>
|
||
<section id="testing-dependencies">
|
||
<h2>Testing Dependencies<a class="headerlink" href="#testing-dependencies" title="Link to this heading">¶</a></h2>
|
||
<p>Dependencies resolve per request, so a route that uses one needs no special
|
||
wiring — register the provider, call the route. And because registering a
|
||
name again replaces the previous provider, you can swap in a fake (a stub
|
||
database, a fixed clock) right inside the test:</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_dependency</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">dependency</span><span class="p">()</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">db</span><span class="p">():</span>
|
||
<span class="k">return</span> <span class="p">{</span><span class="s2">"alice"</span><span class="p">:</span> <span class="p">{</span><span class="s2">"name"</span><span class="p">:</span> <span class="s2">"Alice"</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">"/users/</span><span class="si">{user_id}</span><span class="s2">"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">get_user</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="o">*</span><span class="p">,</span> <span class="n">user_id</span><span class="p">,</span> <span class="n">db</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">db</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">user_id</span><span class="p">,</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">"/users/alice"</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">"name"</span><span class="p">:</span> <span class="s2">"Alice"</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Generator providers run their teardown after the response is sent, so any
|
||
assertion about cleanup belongs <em>after</em> the request returns. App-scoped
|
||
providers (<code class="docutils literal notranslate"><span class="pre">scope="app"</span></code>) only tear down on lifespan shutdown — enter the
|
||
client as a context manager (<code class="docutils literal notranslate"><span class="pre">with</span> <span class="pre">api.requests</span> <span class="pre">as</span> <span class="pre">session:</span></code>) so the
|
||
shutdown actually fires. A Pydantic body parameter always wins over a
|
||
same-named dependency, so an injected body is never shadowed by a provider.</p>
|
||
</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 the same in-process test client. 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="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">api</span><span class="o">.</span><span class="n">test_client</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="async-tests">
|
||
<h2>Async Tests<a class="headerlink" href="#async-tests" title="Link to this heading">¶</a></h2>
|
||
<p>The sync client covers most tests, but some behavior is genuinely async —
|
||
SSE streams, concurrent handlers, app-scoped dependency teardown. For those,
|
||
<code class="docutils literal notranslate"><span class="pre">responder.testing.AsyncTestClient</span></code> is the async mirror of
|
||
<code class="docutils literal notranslate"><span class="pre">api.requests</span></code>: an <code class="docutils literal notranslate"><span class="pre">httpx.AsyncClient</span></code> dispatching requests in-process
|
||
over <code class="docutils literal notranslate"><span class="pre">responder.testing.ASGIStreamingTransport</span></code>. No server, no sockets.
|
||
Unlike <code class="docutils literal notranslate"><span class="pre">httpx.ASGITransport</span></code> — which runs the handler to completion and
|
||
buffers the whole body before returning — this transport returns as soon as
|
||
the response starts and yields body chunks live, so it can read from endless
|
||
SSE streams (heartbeats included) and close them when done. It works with
|
||
plain <code class="docutils literal notranslate"><span class="pre">asyncio.run</span></code> (no pytest plugin required) or with <code class="docutils literal notranslate"><span class="pre">pytest-asyncio</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">asyncio</span>
|
||
<span class="kn">from</span><span class="w"> </span><span class="nn">responder.testing</span><span class="w"> </span><span class="kn">import</span> <span class="n">AsyncTestClient</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">test_async_hello</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">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">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">main</span><span class="p">():</span>
|
||
<span class="k">async</span> <span class="k">with</span> <span class="n">AsyncTestClient</span><span class="p">(</span><span class="n">api</span><span class="p">)</span> <span class="k">as</span> <span class="n">client</span><span class="p">:</span>
|
||
<span class="n">r</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</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="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Entering the client with <code class="docutils literal notranslate"><span class="pre">async</span> <span class="pre">with</span></code> also runs the application’s
|
||
lifespan — startup fires on enter, shutdown on exit — the async counterpart
|
||
of <code class="docutils literal notranslate"><span class="pre">with</span> <span class="pre">api.requests</span> <span class="pre">as</span> <span class="pre">session:</span></code>. Requests made without <code class="docutils literal notranslate"><span class="pre">async</span> <span class="pre">with</span></code>
|
||
work too, but skip lifespan events, just like <code class="docutils literal notranslate"><span class="pre">api.requests</span></code>.</p>
|
||
<p>Because requests run on the test’s own event loop, you can drive several
|
||
in flight at once:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">main</span><span class="p">():</span>
|
||
<span class="k">async</span> <span class="k">with</span> <span class="n">AsyncTestClient</span><span class="p">(</span><span class="n">api</span><span class="p">)</span> <span class="k">as</span> <span class="n">client</span><span class="p">:</span>
|
||
<span class="n">responses</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span>
|
||
<span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"/a"</span><span class="p">),</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"/b"</span><span class="p">),</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"/c"</span><span class="p">),</span>
|
||
<span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="testing-server-sent-events">
|
||
<h2>Testing Server-Sent Events<a class="headerlink" href="#testing-server-sent-events" title="Link to this heading">¶</a></h2>
|
||
<p>Responder ships SSE support via <code class="docutils literal notranslate"><span class="pre">resp.sse</span></code> — and <code class="docutils literal notranslate"><span class="pre">responder.testing</span></code>
|
||
ships the matching parser, so you never hand-roll <code class="docutils literal notranslate"><span class="pre">text/event-stream</span></code>
|
||
parsing in a test. Each parsed event is an <code class="docutils literal notranslate"><span class="pre">SSEEvent</span></code> with <code class="docutils literal notranslate"><span class="pre">data</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">event</span></code> (<code class="docutils literal notranslate"><span class="pre">"message"</span></code> when the server sent no <code class="docutils literal notranslate"><span class="pre">event:</span></code> field), <code class="docutils literal notranslate"><span class="pre">id</span></code>,
|
||
and <code class="docutils literal notranslate"><span class="pre">retry</span></code> attributes; <code class="docutils literal notranslate"><span class="pre">event.json()</span></code> decodes the <code class="docutils literal notranslate"><span class="pre">data</span></code> payload.</p>
|
||
<p>For a finite stream, the sync client buffers the whole body — parse it with
|
||
<code class="docutils literal notranslate"><span class="pre">parse_sse</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">responder.testing</span><span class="w"> </span><span class="kn">import</span> <span class="n">parse_sse</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">test_events</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">"/events"</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">events</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="nd">@resp</span><span class="o">.</span><span class="n">sse</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">stream</span><span class="p">():</span>
|
||
<span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">):</span>
|
||
<span class="k">yield</span> <span class="p">{</span><span class="s2">"data"</span><span class="p">:</span> <span class="p">{</span><span class="s2">"n"</span><span class="p">:</span> <span class="n">n</span><span class="p">},</span> <span class="s2">"id"</span><span class="p">:</span> <span class="n">n</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">"/events"</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">"Content-Type"</span><span class="p">]</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"text/event-stream"</span><span class="p">)</span>
|
||
<span class="n">events</span> <span class="o">=</span> <span class="n">parse_sse</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">events</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
|
||
<span class="k">assert</span> <span class="n">events</span><span class="p">[</span><span class="mi">0</span><span class="p">]</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">"n"</span><span class="p">:</span> <span class="mi">0</span><span class="p">}</span>
|
||
<span class="k">assert</span> <span class="n">events</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">id</span> <span class="o">==</span> <span class="s2">"0"</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>For live or infinite streams, pair <code class="docutils literal notranslate"><span class="pre">AsyncTestClient</span></code> with <code class="docutils literal notranslate"><span class="pre">iter_sse</span></code>,
|
||
which yields events as the server produces them — stop iterating whenever
|
||
you’ve seen enough:</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.testing</span><span class="w"> </span><span class="kn">import</span> <span class="n">AsyncTestClient</span><span class="p">,</span> <span class="n">iter_sse</span>
|
||
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">main</span><span class="p">():</span>
|
||
<span class="k">async</span> <span class="k">with</span> <span class="n">AsyncTestClient</span><span class="p">(</span><span class="n">api</span><span class="p">)</span> <span class="k">as</span> <span class="n">client</span><span class="p">:</span>
|
||
<span class="k">async</span> <span class="k">with</span> <span class="n">client</span><span class="o">.</span><span class="n">stream</span><span class="p">(</span><span class="s2">"GET"</span><span class="p">,</span> <span class="s2">"/events"</span><span class="p">)</span> <span class="k">as</span> <span class="n">r</span><span class="p">:</span>
|
||
<span class="k">async</span> <span class="k">for</span> <span class="n">event</span> <span class="ow">in</span> <span class="n">iter_sse</span><span class="p">(</span><span class="n">r</span><span class="p">):</span>
|
||
<span class="k">assert</span> <span class="n">event</span><span class="o">.</span><span class="n">event</span> <span class="o">==</span> <span class="s2">"message"</span>
|
||
<span class="k">break</span> <span class="c1"># done after the first event</span>
|
||
|
||
<span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">collect_sse(response)</span></code> is the one-shot form, gathering every event from a
|
||
finite stream into a list. Heartbeat comment lines (from
|
||
<code class="docutils literal notranslate"><span class="pre">resp.sse(heartbeat=...)</span></code>) are skipped by the parser, exactly as a
|
||
browser <code class="docutils literal notranslate"><span class="pre">EventSource</span></code> would skip them.</p>
|
||
</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="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">api</span><span class="o">.</span><span class="n">test_client</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">api</span><span class="o">.</span><span class="n">test_client</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>
|
||
<p>Two conveniences make error tests shorter. <code class="docutils literal notranslate"><span class="pre">responder.abort()</span></code> raises a
|
||
rendered HTTP error from anywhere in a handler. By default, framework-generated
|
||
errors use <code class="docutils literal notranslate"><span class="pre">application/problem+json</span></code>; because <code class="docutils literal notranslate"><span class="pre">abort()</span></code> raises a regular
|
||
<code class="docutils literal notranslate"><span class="pre">HTTPException</span></code>, the test client returns it as a response (no
|
||
<code class="docutils literal notranslate"><span class="pre">raise_server_exceptions=False</span></code> needed):</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</span><span class="w"> </span><span class="kn">import</span> <span class="n">abort</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">test_abort</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">"/admin"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">admin</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">abort</span><span class="p">(</span><span class="mi">403</span><span class="p">,</span> <span class="n">detail</span><span class="o">=</span><span class="s2">"Forbidden"</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">"/admin"</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/json"</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">403</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="s2">"title"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"Forbidden"</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="s2">"detail"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"Forbidden"</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>And <code class="docutils literal notranslate"><span class="pre">api.add_exception_handler(exc_or_status,</span> <span class="pre">handler)</span></code> is the imperative
|
||
twin of the <code class="docutils literal notranslate"><span class="pre">@api.exception_handler</span></code> decorator — handy for wiring handlers
|
||
inside a fixture. It accepts an exception class or an integer status code.</p>
|
||
</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://localhost/"</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">before_request</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">"5.0"</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">after_request</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">"5.0"</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">sessions</span><span class="o">=</span><span class="kc">False</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">sessions</span><span class="o">=</span><span class="kc">False</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.
|
||
<code class="docutils literal notranslate"><span class="pre">examples/atelier.py</span></code> is the canonical contract example: it validates the
|
||
generated OpenAPI document and drives a generated client against the same app.
|
||
See <a class="reference internal" href="examples.html"><span class="doc">Examples</span></a> for the rest of the runnable examples.</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>
|
||
<li><p><strong>Quiet the session-key warning.</strong> Any <code class="docutils literal notranslate"><span class="pre">API()</span></code> built without a
|
||
<code class="docutils literal notranslate"><span class="pre">secret_key</span></code> mints a random per-process key and logs a warning. In tests
|
||
that don’t touch sessions, pass <code class="docutils literal notranslate"><span class="pre">sessions=False</span></code>; in tests that do, pass a
|
||
real <code class="docutils literal notranslate"><span class="pre">secret_key=...</span></code> (plus <code class="docutils literal notranslate"><span class="pre">session_https_only=False</span></code> so the cookie
|
||
round-trips over <code class="docutils literal notranslate"><span class="pre">http</span></code>).</p></li>
|
||
<li><p><strong>``req.method`` is uppercase.</strong> It returns a plain <code class="docutils literal notranslate"><span class="pre">str</span></code> such as
|
||
<code class="docutils literal notranslate"><span class="pre">"GET"</span></code> / <code class="docutils literal notranslate"><span class="pre">"POST"</span></code>. Comparisons are case-sensitive, so compare against
|
||
the uppercase form (<code class="docutils literal notranslate"><span class="pre">==</span> <span class="pre">"GET"</span></code>) or call <code class="docutils literal notranslate"><span class="pre">req.method.lower()</span></code> yourself.</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>v9.2.0</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><h3>Navigation</h3>
|
||
<p class="caption" role="heading"><span class="caption-text">User Guide</span></p>
|
||
<ul class="current">
|
||
<li class="toctree-l1"><a class="reference internal" href="quickstart.html">Quick Start</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tour.html">Feature Tour</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Examples</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="routers.html">Composing Apps with Routers</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="guide-config.html">Configuration</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="clientgen.html">Generated Clients</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="deployment.html">Deployment</a></li>
|
||
<li class="toctree-l1 current"><a class="current reference internal" href="#">Testing</a><ul>
|
||
<li class="toctree-l2"><a class="reference internal" href="#getting-started">Getting Started</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#using-fixtures">Using Fixtures</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#overriding-dependencies">Overriding Dependencies</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#testing-json-apis">Testing JSON APIs</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#testing-request-validation">Testing Request Validation</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#testing-file-uploads">Testing File Uploads</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#testing-headers-and-cookies">Testing Headers and Cookies</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#testing-sessions">Testing Sessions</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#testing-dependencies">Testing Dependencies</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#testing-websockets">Testing WebSockets</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#async-tests">Async Tests</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#testing-server-sent-events">Testing Server-Sent Events</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#testing-error-handling">Testing Error Handling</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#testing-lifespan-events">Testing Lifespan Events</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#testing-before-and-after-hooks">Testing Before and After Hooks</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#testing-rate-limiting">Testing Rate Limiting</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#testing-mounted-apps">Testing Mounted Apps</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#tips">Tips</a></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
<p class="caption" role="heading"><span class="caption-text">Tutorials</span></p>
|
||
<ul>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-rest.html">Building a REST API</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-sqlalchemy.html">Using SQLAlchemy</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-auth.html">Authentication</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-websockets.html">WebSocket Tutorial</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-middleware.html">Writing Middleware</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-flask.html">Migrating from Flask</a></li>
|
||
</ul>
|
||
<p class="caption" role="heading"><span class="caption-text">Reference</span></p>
|
||
<ul>
|
||
<li class="toctree-l1"><a class="reference internal" href="api.html">API Reference</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="cli.html">Command Line Interface</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="runtime-contracts.html">Runtime Contracts</a></li>
|
||
</ul>
|
||
<p class="caption" role="heading"><span class="caption-text">Project</span></p>
|
||
<ul>
|
||
<li class="toctree-l1"><a class="reference internal" href="changes.html">Changelog</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="migration-v9.html">Migrating to v9</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="migration-v8.html">Migrating to v8</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="migration-v7.html">Migrating to v7</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="migration-v6.html">Migrating to v6</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="migration-v5.html">Migrating to v5</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="sandbox.html">Sandbox</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="backlog.html">Backlog</a></li>
|
||
</ul>
|
||
|
||
<div class="relations">
|
||
<h3>Related Topics</h3>
|
||
<ul>
|
||
<li><a href="index.html">Documentation overview</a><ul>
|
||
<li>Previous: <a href="deployment.html" title="previous chapter">Deployment</a></li>
|
||
<li>Next: <a href="tutorial-rest.html" title="next chapter">Building a REST API</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>
|
||
|
||
|
||
<a href="https://github.com/kennethreitz/responder" class="github">
|
||
<img src="_static/forkme_right_darkblue.png" alt="Fork me on GitHub" class="github"/>
|
||
</a>
|
||
|
||
|
||
|
||
</body>
|
||
</html> |