<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>textbook</title><link href="https://blog.jonrshar.pe/" rel="alternate"></link><link href="https://blog.jonrshar.pe/feeds/all.atom.xml" rel="self"></link><id>https://blog.jonrshar.pe/</id><updated>2025-05-18T09:45:00+01:00</updated><subtitle>These are my opinions - if you don't like them, I have others</subtitle><entry><title>Next.js and Prisma in Docker</title><link href="https://blog.jonrshar.pe/2024/Dec/24/nextjs-prisma-docker.html" rel="alternate"></link><published>2024-12-24T11:30:00+00:00</published><updated>2024-12-24T12:00:00+00:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2024-12-24:/2024/Dec/24/nextjs-prisma-docker.html</id><summary type="html">&lt;p&gt;Containerisation patterns for a Next.js + Prisma full-stack application.&lt;/p&gt;</summary><content type="html">&lt;p&gt;For my work with &lt;a href="https://codeyourfuture.io/"&gt;CodeYourFuture&lt;/a&gt;, earlier this year I helped one of the product teams I work with to put together a &lt;code&gt;Dockerfile&lt;/code&gt; for their application, which was based on &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; and &lt;a href="https://www.prisma.io/"&gt;Prisma&lt;/a&gt;.
This proved to be a little trickier than I'd anticipated, so I wanted to document what we figured out.&lt;/p&gt;
&lt;p&gt;This is going to focus on building a simple app, designed to be deployed as a single container to e.g. a Kubernetes cluster, but the commentary should help signpost where updates are needed for other deployment topologies.&lt;/p&gt;
&lt;h2&gt;Create Next.js app&lt;/h2&gt;
&lt;p&gt;To start off let's create a brand new Next.js project:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;npx&lt;span class="w"&gt; &lt;/span&gt;create-next-app@latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Make your own selections for TypeScript, &lt;a href="https://nextjs.org/docs#app-router-vs-pages-router"&gt;app vs. pages routing&lt;/a&gt;, whether to apply ESLint, etc.
But I would recommend using the &lt;a href="https://nextjs.org/docs/app/building-your-application/configuring/src-directory"&gt;&lt;code&gt;src/&lt;/code&gt; directory&lt;/a&gt;, as this simplifies copying your code into the container later on.&lt;/p&gt;
&lt;h3&gt;npm configuration&lt;/h3&gt;
&lt;p&gt;For the sake of stable, reproducible builds, you can then add an explicit &lt;a href="https://docs.npmjs.com/cli/v10/configuring-npm/package-json#engines"&gt;&lt;code&gt;engines&lt;/code&gt; field&lt;/a&gt;, e.g. by running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;pkg&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;engines.node=^22.11&amp;#39;&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--package-lock-only&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# sync package-lock.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;which will update &lt;code&gt;package.json&lt;/code&gt; as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gi"&gt;+   },&lt;/span&gt;
&lt;span class="gi"&gt;+   &amp;quot;engines&amp;quot;: {&lt;/span&gt;
&lt;span class="gi"&gt;+     &amp;quot;node&amp;quot;: &amp;quot;^22.11&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;   }
&lt;span class="w"&gt; &lt;/span&gt; }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next tell npm you want it to validate the Node version when e.g. installing dependencies:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;--location&lt;span class="o"&gt;=&lt;/span&gt;project&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;engine-strict&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;which creates an &lt;code&gt;.npmrc&lt;/code&gt; file containing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="na"&gt;engine-strict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Build output types&lt;/h3&gt;
&lt;p&gt;The first trick to a good Next.js image is to leverage &lt;a href="https://nextjs.org/docs/app/api-reference/next-config-js/output#automatically-copying-traced-files"&gt;standalone mode&lt;/a&gt;, where it creates (in &lt;code&gt;.next/standalone&lt;/code&gt;) an app that can be deployed as-is, including only the dependencies required at runtime in a cut-down &lt;code&gt;node_modules/&lt;/code&gt;.
To enable this mode, update &lt;code&gt;next.config.mjs&lt;/code&gt; as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt; /** @type {import(&amp;#39;next&amp;#39;).NextConfig} */
&lt;span class="gd"&gt;- const nextConfig = {};&lt;/span&gt;
&lt;span class="gi"&gt;+ const nextConfig = {&lt;/span&gt;
&lt;span class="gi"&gt;+   output: &amp;#39;standalone&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+ };&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt; export default nextConfig;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now when you run the build:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;build

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;demo-app@0.1.0&lt;span class="w"&gt; &lt;/span&gt;build
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;next&lt;span class="w"&gt; &lt;/span&gt;build

&lt;span class="w"&gt;   &lt;/span&gt;▲&lt;span class="w"&gt; &lt;/span&gt;Next.js&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;.1.2

&lt;span class="w"&gt;   &lt;/span&gt;Creating&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;optimized&lt;span class="w"&gt; &lt;/span&gt;production&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;...
&lt;span class="w"&gt; &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;Compiled&lt;span class="w"&gt; &lt;/span&gt;successfully
&lt;span class="w"&gt; &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;Linting&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;checking&lt;span class="w"&gt; &lt;/span&gt;validity&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;types&lt;span class="w"&gt;    &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;Collecting&lt;span class="w"&gt; &lt;/span&gt;page&lt;span class="w"&gt; &lt;/span&gt;data&lt;span class="w"&gt;    &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;Generating&lt;span class="w"&gt; &lt;/span&gt;static&lt;span class="w"&gt; &lt;/span&gt;pages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;/5&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;Collecting&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;traces&lt;span class="w"&gt;    &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;Finalizing&lt;span class="w"&gt; &lt;/span&gt;page&lt;span class="w"&gt; &lt;/span&gt;optimization&lt;span class="w"&gt;    &lt;/span&gt;

Route&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;app&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                              &lt;/span&gt;Size&lt;span class="w"&gt;     &lt;/span&gt;First&lt;span class="w"&gt; &lt;/span&gt;Load&lt;span class="w"&gt; &lt;/span&gt;JS
┌&lt;span class="w"&gt; &lt;/span&gt;○&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt;                                    &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.62&lt;span class="w"&gt; &lt;/span&gt;kB&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;111&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kB
└&lt;span class="w"&gt; &lt;/span&gt;○&lt;span class="w"&gt; &lt;/span&gt;/_not-found&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="m"&gt;979&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;B&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="m"&gt;106&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kB
+&lt;span class="w"&gt; &lt;/span&gt;First&lt;span class="w"&gt; &lt;/span&gt;Load&lt;span class="w"&gt; &lt;/span&gt;JS&lt;span class="w"&gt; &lt;/span&gt;shared&lt;span class="w"&gt; &lt;/span&gt;by&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="m"&gt;105&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kB
&lt;span class="w"&gt;  &lt;/span&gt;├&lt;span class="w"&gt; &lt;/span&gt;chunks/4bd1b696-20882bf820444624.js&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;52&lt;/span&gt;.9&lt;span class="w"&gt; &lt;/span&gt;kB
&lt;span class="w"&gt;  &lt;/span&gt;├&lt;span class="w"&gt; &lt;/span&gt;chunks/517-cf5b1ec733e34704.js&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;.5&lt;span class="w"&gt; &lt;/span&gt;kB
&lt;span class="w"&gt;  &lt;/span&gt;└&lt;span class="w"&gt; &lt;/span&gt;other&lt;span class="w"&gt; &lt;/span&gt;shared&lt;span class="w"&gt; &lt;/span&gt;chunks&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;total&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.89&lt;span class="w"&gt; &lt;/span&gt;kB


○&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Static&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;prerendered&lt;span class="w"&gt; &lt;/span&gt;as&lt;span class="w"&gt; &lt;/span&gt;static&lt;span class="w"&gt; &lt;/span&gt;content
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;you can test out the standalone app by running its &lt;code&gt;server.js&lt;/code&gt; entrypoint:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;.next/standalone/server.js
&lt;span class="w"&gt;   &lt;/span&gt;▲&lt;span class="w"&gt; &lt;/span&gt;Next.js&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;.1.2
&lt;span class="w"&gt;   &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Local:&lt;span class="w"&gt;        &lt;/span&gt;http://localhost:3000
&lt;span class="w"&gt;   &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Network:&lt;span class="w"&gt;      &lt;/span&gt;http://0.0.0.0:3000

&lt;span class="w"&gt; &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;Starting...
&lt;span class="w"&gt; &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;Ready&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;78ms
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you &lt;em&gt;visit&lt;/em&gt; that site, though, it will look a little bit weird:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.jonrshar.pe/images/next-standalone.png"&gt;&lt;img alt="Screenshot of an empty Next.js standalone app" src="https://blog.jonrshar.pe/images/next-standalone.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Next.js separates out the &lt;code&gt;public/&lt;/code&gt; and &lt;code&gt;.next/static/&lt;/code&gt; directories, intending these to be served separately via CDN; we'll deal with this when building the Docker image.&lt;/p&gt;
&lt;h2&gt;Next.js Docker image&lt;/h2&gt;
&lt;p&gt;Next.js does have &lt;a href="https://nextjs.org/docs/pages/building-your-application/deploying#docker-image"&gt;a page&lt;/a&gt; on deploying in Docker, which points to some examples.
We'll work through &lt;a href="https://github.com/vercel/next.js/blob/430e71a38db549e99d8daad2caac160316aa30a1/examples/with-docker/Dockerfile"&gt;this example&lt;/a&gt; section by section, with some changes I've added on top based on my own experience and the &lt;a href="https://github.com/nodejs/docker-node/blob/1ed1dd596c1f074bbfe907381e8b7f28866faa54/docs/BestPractices.md"&gt;Docker and Node.js Best Practices&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;

&lt;span class="c"&gt;# Install dependencies only when needed&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;deps&lt;/span&gt;
&lt;span class="c"&gt;# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.&lt;/span&gt;
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apk&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;--no-cache&lt;span class="w"&gt; &lt;/span&gt;libc6-compat
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/app&lt;/span&gt;

&lt;span class="c"&gt;# Install dependencies based on the preferred package manager&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;package.json&lt;span class="w"&gt; &lt;/span&gt;yarn.lock*&lt;span class="w"&gt; &lt;/span&gt;package-lock.json*&lt;span class="w"&gt; &lt;/span&gt;pnpm-lock.yaml*&lt;span class="w"&gt; &lt;/span&gt;./
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;yarn.lock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;yarn&lt;span class="w"&gt; &lt;/span&gt;--frozen-lockfile&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;elif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;package-lock.json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;ci&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;elif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;pnpm-lock.yaml&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;corepack&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pnpm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pnpm&lt;span class="w"&gt; &lt;/span&gt;i&lt;span class="w"&gt; &lt;/span&gt;--frozen-lockfile&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Lockfile not found.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;


&lt;span class="c"&gt;# Rebuild the source code only when needed&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--from&lt;span class="o"&gt;=&lt;/span&gt;deps&lt;span class="w"&gt; &lt;/span&gt;/app/node_modules&lt;span class="w"&gt; &lt;/span&gt;./node_modules
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;.

&lt;span class="c"&gt;# Next.js collects completely anonymous telemetry data about general usage.&lt;/span&gt;
&lt;span class="c"&gt;# Learn more here: https://nextjs.org/telemetry&lt;/span&gt;
&lt;span class="c"&gt;# Uncomment the following line in case you want to disable telemetry during the build.&lt;/span&gt;
&lt;span class="c"&gt;# ENV NEXT_TELEMETRY_DISABLED 1&lt;/span&gt;

&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;yarn.lock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;yarn&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;elif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;package-lock.json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;elif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;pnpm-lock.yaml&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;corepack&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pnpm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pnpm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Lockfile not found.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Production image, copy all the files and run next&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runner&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/app&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;NODE_ENV&lt;span class="w"&gt; &lt;/span&gt;production
&lt;span class="c"&gt;# Uncomment the following line in case you want to disable telemetry during runtime.&lt;/span&gt;
&lt;span class="c"&gt;# ENV NEXT_TELEMETRY_DISABLED 1&lt;/span&gt;

&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;addgroup&lt;span class="w"&gt; &lt;/span&gt;--system&lt;span class="w"&gt; &lt;/span&gt;--gid&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1001&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nodejs
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;adduser&lt;span class="w"&gt; &lt;/span&gt;--system&lt;span class="w"&gt; &lt;/span&gt;--uid&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1001&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nextjs

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--from&lt;span class="o"&gt;=&lt;/span&gt;builder&lt;span class="w"&gt; &lt;/span&gt;/app/public&lt;span class="w"&gt; &lt;/span&gt;./public

&lt;span class="c"&gt;# Set the correct permission for prerender cache&lt;/span&gt;
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;.next
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;chown&lt;span class="w"&gt; &lt;/span&gt;nextjs:nodejs&lt;span class="w"&gt; &lt;/span&gt;.next

&lt;span class="c"&gt;# Automatically leverage output traces to reduce image size&lt;/span&gt;
&lt;span class="c"&gt;# https://nextjs.org/docs/advanced-features/output-file-tracing&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--from&lt;span class="o"&gt;=&lt;/span&gt;builder&lt;span class="w"&gt; &lt;/span&gt;--chown&lt;span class="o"&gt;=&lt;/span&gt;nextjs:nodejs&lt;span class="w"&gt; &lt;/span&gt;/app/.next/standalone&lt;span class="w"&gt; &lt;/span&gt;./
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--from&lt;span class="o"&gt;=&lt;/span&gt;builder&lt;span class="w"&gt; &lt;/span&gt;--chown&lt;span class="o"&gt;=&lt;/span&gt;nextjs:nodejs&lt;span class="w"&gt; &lt;/span&gt;/app/.next/static&lt;span class="w"&gt; &lt;/span&gt;./.next/static

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;nextjs&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;3000&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PORT&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3000&lt;/span&gt;

&lt;span class="c"&gt;# server.js is created by next build from the standalone output&lt;/span&gt;
&lt;span class="c"&gt;# https://nextjs.org/docs/pages/api-reference/next-config-js/output&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;HOSTNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0.0.0.0&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;server.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This uses a good pattern for building Docker images, a &lt;a href="https://docs.docker.com/build/building/multi-stage/"&gt;multi-stage build&lt;/a&gt;, to maximise the efficiency of Docker's layer caching and keep the final artifact as small as possible.
However, it also includes some unnecessary steps and complexity that practical builds will not need.&lt;/p&gt;
&lt;h3&gt;Setting up the image&lt;/h3&gt;
&lt;p&gt;For the sake of efficiency, reducing the number of files copied into the &lt;a href="https://docs.docker.com/build/building/context/"&gt;build context&lt;/a&gt;, create a &lt;code&gt;.dockerignore&lt;/code&gt; file containing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="na"&gt;.next&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;
&lt;span class="nf"&gt;node_modules&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then the first stage in our &lt;code&gt;Dockerfile&lt;/code&gt; will be defining a consistent base image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;NODE_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;jod

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:${NODE_VERSION}-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Specifying the &lt;code&gt;NODE_VERSION&lt;/code&gt; &lt;a href="https://docs.docker.com/build/guide/build-args/"&gt;build &lt;code&gt;ARG&lt;/code&gt;&lt;/a&gt; allows the Node version to be overridden at build time with the &lt;code&gt;--build-arg&lt;/code&gt; flag.
For example, one way to utilise this if you have a &lt;a href="https://github.com/nvm-sh/nvm?tab=readme-ov-file#nvmrc"&gt;&lt;code&gt;.nvmrc&lt;/code&gt;&lt;/a&gt; file to manage your Node versions is to &lt;code&gt;docker build --build-arg "NODE_VERSION=$(cat .nvmrc)" ...&lt;/code&gt;.
It defaults to the latest release of the current LTS (long-term support) version, Node 22 "Jod" (a common German-derived name for &lt;a href="https://en.wikipedia.org/wiki/Iodine"&gt;iodine&lt;/a&gt;, in keeping with the "elements" naming theme), rather than the older Node 18 "Hydrogen" line.&lt;/p&gt;
&lt;p&gt;We then use this version with the &lt;code&gt;-alpine&lt;/code&gt; variant, built on &lt;a href="https://www.alpinelinux.org/"&gt;Alpine Linux&lt;/a&gt;, which leads to much smaller images than the default Debian-based images (e.g. &lt;code&gt;node:22.12.0&lt;/code&gt; on Debian 12 "Bookworm" is 1.6GB, whereas &lt;code&gt;node:22.12.0-alpine&lt;/code&gt; is 221MB).
This can &lt;em&gt;occasionally&lt;/em&gt; cause build problems; see the official notes &lt;a href="https://github.com/nodejs/docker-node/tree/1ed1dd596c1f074bbfe907381e8b7f28866faa54?tab=readme-ov-file#nodealpine"&gt;here&lt;/a&gt; and consider either adding the compatibility libraries or switching to the &lt;code&gt;-slim&lt;/code&gt; variant (341MB for the same Node.js version) if you have an issue.&lt;/p&gt;
&lt;h3&gt;Building the application&lt;/h3&gt;
&lt;p&gt;Splitting up dependency installation and building into two separate stages seems unnecessary here.
The advantage of having a separate dependency stage is that when you copy the &lt;code&gt;node_modules/&lt;/code&gt; through you don't also get npm's &lt;em&gt;cache&lt;/em&gt; of files it downloaded.
But here we're only going to copy &lt;code&gt;.next/standalone&lt;/code&gt; (which has its own &lt;code&gt;node_modules/&lt;/code&gt;) and a few other selected directories into the last stage anyway.&lt;/p&gt;
&lt;p&gt;So we're going to use the &lt;code&gt;base&lt;/code&gt; image directly and install the dependencies with npm's &lt;a href="https://docs.npmjs.com/cli/v10/commands/npm-ci"&gt;"clean install"&lt;/a&gt; (which follows the lockfile exactly and bails out if it's missing or out-of-sync).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;.npmrc&lt;span class="w"&gt; &lt;/span&gt;package*.json&lt;span class="w"&gt; &lt;/span&gt;./
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;--no-fund&lt;span class="w"&gt; &lt;/span&gt;--no-update-notifier&lt;span class="w"&gt; &lt;/span&gt;ci
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that we &lt;strong&gt;only&lt;/strong&gt; copy in the relevant files for npm (&lt;code&gt;.npmrc&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt;) before installing the dependencies.
This allows Docker to &lt;em&gt;cache&lt;/em&gt; the installation layer, such that it only re-runs the installation process if one of those three files changes.
For changes to the app code that &lt;em&gt;don't&lt;/em&gt; involve changing the dependencies, this means much faster rebuilds.&lt;/p&gt;
&lt;p&gt;We don't bother with the conditional logic to handle multiple possible package managers, just including the one that's relevant for the current project.
Setting the &lt;a href="https://docs.npmjs.com/cli/v10/using-npm/config#fund"&gt;&lt;code&gt;fund&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://docs.npmjs.com/cli/v10/using-npm/config#update-notifier"&gt;&lt;code&gt;update-notifier&lt;/code&gt;&lt;/a&gt; flags disables the &lt;em&gt;"packages are looking for funding"&lt;/em&gt; and &lt;em&gt;"New minor version of npm available!"&lt;/em&gt; messages, which aren't relevant in a non-interactive context.&lt;/p&gt;
&lt;p&gt;With our dependencies available, we can re-run the build, this time &lt;em&gt;inside&lt;/em&gt; the container we're building:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;public/&lt;span class="w"&gt; &lt;/span&gt;./public
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/&lt;span class="w"&gt; &lt;/span&gt;./src
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;next.config.mjs&lt;span class="w"&gt; &lt;/span&gt;./
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;NEXT_TELEMETRY_DISABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Again only the necessary files are copied in before the build takes place, so the layers can be cached appropriately - this is why the &lt;code&gt;src/&lt;/code&gt; directory option was recommended above, it's much simpler than copying in multiple directories (&lt;code&gt;app/&lt;/code&gt;, &lt;code&gt;components/&lt;/code&gt;, &lt;code&gt;lib/&lt;/code&gt;, etc.) when using common &lt;a href="https://nextjs.org/docs/app/building-your-application/routing/colocation#store-project-files-outside-of-app"&gt;organisation patterns&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; if you have issues due to missing build tools during install or build, you may need to add &lt;code&gt;RUN apk add --no-cache libc6-compat&lt;/code&gt; back in at the start of this stage.&lt;/p&gt;
&lt;h3&gt;Creating the final image&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;

&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apk&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;--no-cache&lt;span class="w"&gt; &lt;/span&gt;tini

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--from&lt;span class="o"&gt;=&lt;/span&gt;builder&lt;span class="w"&gt; &lt;/span&gt;--chown&lt;span class="o"&gt;=&lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;/app/.next/standalone&lt;span class="w"&gt; &lt;/span&gt;./
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--from&lt;span class="o"&gt;=&lt;/span&gt;builder&lt;span class="w"&gt; &lt;/span&gt;--chown&lt;span class="o"&gt;=&lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;/app/.next/static&lt;span class="w"&gt; &lt;/span&gt;./.next/static
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--from&lt;span class="o"&gt;=&lt;/span&gt;builder&lt;span class="w"&gt; &lt;/span&gt;--chown&lt;span class="o"&gt;=&lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;/app/public&lt;span class="w"&gt; &lt;/span&gt;./public

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;start.sh&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;HOSTNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;NEXT_TELEMETRY_DISABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3000&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;3000&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;start.sh&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Node.js is &lt;a href="https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#handling-kernel-signals"&gt;not designed&lt;/a&gt; to be run as the root process, we start by adding &lt;a href="https://github.com/krallin/tini"&gt;&lt;code&gt;tini&lt;/code&gt;&lt;/a&gt; to run it (this is built into Docker as of v1.13, but other environments might not have it).&lt;/p&gt;
&lt;p&gt;As previously mentioned a standalone build is set up for some files to be deployed from a CDN, but we want a single deployment artifact. So from the previous stage we copy all of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The standalone build itself, &lt;code&gt;.next/standalone/&lt;/code&gt;, including the server and minimal dependencies;&lt;/li&gt;
&lt;li&gt;The static outputs, &lt;code&gt;.next/static/&lt;/code&gt;, e.g. compiled client-side JS and CSS; and&lt;/li&gt;
&lt;li&gt;The public directory, &lt;code&gt;public/&lt;/code&gt;, for static assets like images and e.g. &lt;code&gt;robots.txt&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are all colocated in the same directory, so Next.js can find and serve these assets alongside API endpoints.
As each one is copied in, its ownership is allocated to the &lt;a href="https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#non-root-user"&gt;non-root user&lt;/a&gt; so the container can run under the &lt;a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege"&gt;principle of least privilege&lt;/a&gt;.
However, we don't need to create another non-root user and associated group specifically for Next.js.&lt;/p&gt;
&lt;p&gt;Once we have the build outputs, we add a script to actually start the application when the container is created.
That script, &lt;code&gt;start.sh&lt;/code&gt;, should look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-ex

&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/sbin/tini&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;server.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ensure this script is user-executable by running &lt;code&gt;chmod u+x start.sh&lt;/code&gt; once you've created it. 
So far this is pretty minimal, all it does is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Declare the appropriate script type with a &lt;a href="https://en.wikipedia.org/wiki/Shebang_(Unix)"&gt;shebang&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;Set some basic flags to exit the shell script if any individual step has an error (&lt;code&gt;-e&lt;/code&gt;) and print out each command before running it (&lt;code&gt;-x&lt;/code&gt;); and&lt;/li&gt;
&lt;li&gt;Use the aforementioned &lt;code&gt;tini&lt;/code&gt; to run our server with Node.js.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By copying it into &lt;code&gt;/usr/local/bin&lt;/code&gt; it's available on the &lt;code&gt;PATH&lt;/code&gt;, hence the initial entrypoint of the container can simply be &lt;code&gt;start.sh&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next we set some environment variables, to give the app appropriate configuration for running in the container, plus some metadata to tell other tools what ports will be exposed.
Finally we switch to the non-root user, as discussed above, and declare the script as the default entrypoint.&lt;/p&gt;
&lt;h3&gt;Testing the container&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Build the image&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;--tag&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;nextjs-image&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;.

&lt;span class="c1"&gt;# Run the image&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--publish&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3000&lt;/span&gt;:3000&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;nextjs-image&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;While the app is running with the default entrypoint, you can visit the app on localhost at port 3000; it should now appear with the correct styling and images.&lt;/p&gt;
&lt;h2&gt;Prisma&lt;/h2&gt;
&lt;p&gt;Prisma is an ORM (object-relational mapper) - you describe the shape of your data (in &lt;code&gt;schema.prisma&lt;/code&gt;) and it handles validating, storing and retrieving objects in the database.
It generates a type-safe client (using TypeScript types) matching your schema, to give better IDE support while interacting with the database.
It also generates migrations for you, so that as you &lt;a href="https://martinfowler.com/articles/evodb.html"&gt;evolve&lt;/a&gt; your schema you can keep databases in different environments up-to-date.&lt;/p&gt;
&lt;h3&gt;Installation&lt;/h3&gt;
&lt;p&gt;We can add Prisma to the app by following the &lt;a href="https://www.prisma.io/docs/getting-started/quickstart"&gt;quickstart&lt;/a&gt; - I'm using Postgres, but other providers are also supported:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Install Prisma CLI&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;prisma&lt;span class="w"&gt; &lt;/span&gt;--save-dev

&lt;span class="c1"&gt;# Set up basic structure&lt;/span&gt;
npx&lt;span class="w"&gt; &lt;/span&gt;prisma&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;--datasource-provider&lt;span class="w"&gt; &lt;/span&gt;postgresql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here you can already see hints of the first conflict:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;warn&lt;span class="w"&gt; &lt;/span&gt;You&lt;span class="w"&gt; &lt;/span&gt;already&lt;span class="w"&gt; &lt;/span&gt;have&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;.gitignore&lt;span class="w"&gt; &lt;/span&gt;file.&lt;span class="w"&gt; &lt;/span&gt;Don&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t&lt;span class="w"&gt; &lt;/span&gt;forget&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;.env&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;it&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;any&lt;span class="w"&gt; &lt;/span&gt;private&lt;span class="w"&gt; &lt;/span&gt;information.

Next&lt;span class="w"&gt; &lt;/span&gt;steps:
&lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;Set&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;DATABASE_URL&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;.env&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;point&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;your&lt;span class="w"&gt; &lt;/span&gt;existing&lt;span class="w"&gt; &lt;/span&gt;database.&lt;span class="w"&gt; &lt;/span&gt;...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Whereas Next.js handles &lt;a href="https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables"&gt;multiple &lt;code&gt;.env&lt;/code&gt; files&lt;/a&gt;, Prisma assumes &lt;a href="https://www.prisma.io/docs/orm/more/development-environment/environment-variables/env-files"&gt;only &lt;code&gt;.env&lt;/code&gt;&lt;/a&gt; will exist (although it checks for it in a few locations).
Also Next.js assumes that only the &lt;code&gt;.local&lt;/code&gt; versions of the files should be considered secret.
It includes the following in &lt;code&gt;.gitignore&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# env files (can opt-in for committing if needed)&lt;/span&gt;
&lt;span class="na"&gt;.env&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;but adds in the documentation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Good to know&lt;/strong&gt;: &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;.env.development&lt;/code&gt;, and &lt;code&gt;.env.production&lt;/code&gt; files should be included in your repository as they define defaults.
All &lt;code&gt;.env&lt;/code&gt; files are excluded in &lt;code&gt;.gitignore&lt;/code&gt; by default, allowing you to opt-into committing these values to your repository.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;By contrast, Prisma suggests placing your &lt;code&gt;DATABASE_URL&lt;/code&gt;, which should not be tracked, in a &lt;em&gt;non&lt;/em&gt;-&lt;code&gt;.local&lt;/code&gt; &lt;code&gt;.env&lt;/code&gt; file.
To deal with this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rename the &lt;code&gt;.env&lt;/code&gt; file Prisma created to &lt;code&gt;.env.local&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Install &lt;code&gt;dotenv-cli&lt;/code&gt;, as Prisma's &lt;a href="https://www.prisma.io/docs/orm/more/development-environment/environment-variables/managing-env-files-and-setting-variables#manage-env-files-manually"&gt;docs&lt;/a&gt; recommend; and&lt;/li&gt;
&lt;li&gt;Add a helper script entrypoint to run Prisma with the &lt;code&gt;-c&lt;/code&gt;ascading env files loaded.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mv&lt;span class="w"&gt; &lt;/span&gt;.env&lt;span class="o"&gt;{&lt;/span&gt;,.local&lt;span class="o"&gt;}&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;dotenv-cli
$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;pkg&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;scripts.prisma=dotenv -c -- prisma&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Update &lt;code&gt;.env.local&lt;/code&gt; to set up a valid connection string for whichever data source you've selected.
Now e.g. &lt;code&gt;npm run prisma -- migrate dev&lt;/code&gt; will try to set up the configured DB:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;prisma&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;migrate&lt;span class="w"&gt; &lt;/span&gt;dev

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;my-app@0.1.0&lt;span class="w"&gt; &lt;/span&gt;prisma
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;dotenv&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;prisma&lt;span class="w"&gt; &lt;/span&gt;migrate&lt;span class="w"&gt; &lt;/span&gt;dev

Prisma&lt;span class="w"&gt; &lt;/span&gt;schema&lt;span class="w"&gt; &lt;/span&gt;loaded&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;prisma/schema.prisma
Datasource&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;db&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;PostgreSQL&lt;span class="w"&gt; &lt;/span&gt;database&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;prisma&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;schema&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;public&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;localhost:5432&amp;quot;&lt;/span&gt;

Already&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sync,&lt;span class="w"&gt; &lt;/span&gt;no&lt;span class="w"&gt; &lt;/span&gt;schema&lt;span class="w"&gt; &lt;/span&gt;change&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;pending&lt;span class="w"&gt; &lt;/span&gt;migration&lt;span class="w"&gt; &lt;/span&gt;was&lt;span class="w"&gt; &lt;/span&gt;found.

Running&lt;span class="w"&gt; &lt;/span&gt;generate...&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Use&lt;span class="w"&gt; &lt;/span&gt;--skip-generate&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;skip&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;generators&lt;span class="o"&gt;)&lt;/span&gt;
Error:&lt;span class="w"&gt; &lt;/span&gt;
You&lt;span class="w"&gt; &lt;/span&gt;don&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t&lt;span class="w"&gt; &lt;/span&gt;have&lt;span class="w"&gt; &lt;/span&gt;any&lt;span class="w"&gt; &lt;/span&gt;models&lt;span class="w"&gt; &lt;/span&gt;defined&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;your&lt;span class="w"&gt; &lt;/span&gt;schema.prisma,&lt;span class="w"&gt; &lt;/span&gt;so&lt;span class="w"&gt; &lt;/span&gt;nothing&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;generated.
You&lt;span class="w"&gt; &lt;/span&gt;can&lt;span class="w"&gt; &lt;/span&gt;define&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;model&lt;span class="w"&gt; &lt;/span&gt;like&lt;span class="w"&gt; &lt;/span&gt;this:

model&lt;span class="w"&gt; &lt;/span&gt;User&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;id&lt;span class="w"&gt;    &lt;/span&gt;Int&lt;span class="w"&gt;     &lt;/span&gt;@id&lt;span class="w"&gt; &lt;/span&gt;@default&lt;span class="o"&gt;(&lt;/span&gt;autoincrement&lt;span class="o"&gt;())&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;email&lt;span class="w"&gt; &lt;/span&gt;String&lt;span class="w"&gt;  &lt;/span&gt;@unique
&lt;span class="w"&gt;  &lt;/span&gt;name&lt;span class="w"&gt;  &lt;/span&gt;String?
&lt;span class="o"&gt;}&lt;/span&gt;

More&lt;span class="w"&gt; &lt;/span&gt;information&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;our&lt;span class="w"&gt; &lt;/span&gt;documentation:
https://pris.ly/d/prisma-schema
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Add the suggested &lt;code&gt;User&lt;/code&gt; model to &lt;code&gt;prisma/schema.prisma&lt;/code&gt;, then re-run the dev migration command - when prompted, enter e.g. &lt;em&gt;"create user"&lt;/em&gt; as the migration name.
Something like the following will be generated in &lt;code&gt;prisma/migrations/&amp;lt;timestamp&amp;gt;_create_user.sql&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;-- CreateTable&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;User&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;SERIAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;email&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;CONSTRAINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;User_pkey&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- CreateIndex&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;User_email_key&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;User&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;email&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and you'll be told:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Your database is now in sync with your schema.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Add a &lt;a href="https://docs.npmjs.com/cli/v10/using-npm/scripts?v=true#pre--post-scripts"&gt;&lt;code&gt;pre-&lt;/code&gt; script&lt;/a&gt; to ensure the Prisma client is always regenerated when the app is built, then re-run the Next.js build:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;pkg&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;scripts.prebuild=prisma generate&amp;#39;&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code&gt;src/app/api/users/route.js&lt;/code&gt;, write the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PrismaClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@prisma/client&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PrismaClient&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that &lt;code&gt;.user&lt;/code&gt; is autocompleted by your IDE; this is the benefit of &lt;code&gt;prisma generate&lt;/code&gt;-ing the client, which includes type definitions, from the schema.
If you &lt;code&gt;npm run dev&lt;/code&gt;, you should be able to both visit the home page and hit this new API endpoint:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;--silent&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://localhost:3000/api/users&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;users&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As we're now using the Prisma client in the Next.js code, when you re-run &lt;code&gt;npm run build&lt;/code&gt; your &lt;code&gt;.next/standalone/node_modules/&lt;/code&gt; should now include &lt;code&gt;.prisma/&lt;/code&gt; (the generated client) and &lt;code&gt;@prisma/&lt;/code&gt; (the core library).
We also want to make sure the Prisma files are available at image build time, so the client can be generated.
In the &lt;code&gt;Dockerfile&lt;/code&gt;, add:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt; RUN npm --no-fund --no-update-notifier ci

&lt;span class="gi"&gt;+ COPY prisma/ ./prisma&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt; COPY public/ ./public
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Aside: Next.js 14&lt;/h3&gt;
&lt;p&gt;When we try to &lt;code&gt;docker build&lt;/code&gt; the container, if using Next.js v14, it fails:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="m"&gt;11&lt;/span&gt;.29&lt;span class="w"&gt; &lt;/span&gt;Error&lt;span class="w"&gt; &lt;/span&gt;occurred&lt;span class="w"&gt; &lt;/span&gt;prerendering&lt;span class="w"&gt; &lt;/span&gt;page&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/api/users&amp;quot;&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;Read&lt;span class="w"&gt; &lt;/span&gt;more:&lt;span class="w"&gt; &lt;/span&gt;https://nextjs.org/docs/messages/prerender-error
&lt;span class="m"&gt;11&lt;/span&gt;.29&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="m"&gt;11&lt;/span&gt;.29&lt;span class="w"&gt; &lt;/span&gt;PrismaClientInitializationError:&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="m"&gt;11&lt;/span&gt;.29&lt;span class="w"&gt; &lt;/span&gt;Invalid&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;prisma.user.findMany&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;invocation:
&lt;span class="m"&gt;11&lt;/span&gt;.29&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="m"&gt;11&lt;/span&gt;.29&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="m"&gt;11&lt;/span&gt;.29&lt;span class="w"&gt; &lt;/span&gt;error:&lt;span class="w"&gt; &lt;/span&gt;Environment&lt;span class="w"&gt; &lt;/span&gt;variable&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;found:&lt;span class="w"&gt; &lt;/span&gt;DATABASE_URL.
&lt;span class="m"&gt;11&lt;/span&gt;.29&lt;span class="w"&gt;   &lt;/span&gt;--&amp;gt;&lt;span class="w"&gt;  &lt;/span&gt;schema.prisma:13
&lt;span class="m"&gt;11&lt;/span&gt;.29&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="m"&gt;11&lt;/span&gt;.29&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nv"&gt;provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;postgresql&amp;quot;&lt;/span&gt;
&lt;span class="m"&gt;11&lt;/span&gt;.29&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DATABASE_URL&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;11&lt;/span&gt;.29&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="m"&gt;11&lt;/span&gt;.29&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="m"&gt;11&lt;/span&gt;.29&lt;span class="w"&gt; &lt;/span&gt;Validation&lt;span class="w"&gt; &lt;/span&gt;Error&lt;span class="w"&gt; &lt;/span&gt;Count:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To be able to pre-render and cache all of the API routes, Next.js tries to invoke them all at build time.
As that now involves retrieving user records from the database, Prisma is trying to do just that.
Inside the container, though, no connection string for the database is available, so it bails out and the build fails.&lt;/p&gt;
&lt;p&gt;It's possible to solve this by adding another &lt;a href="https://docs.docker.com/build/guide/build-args/"&gt;build argument&lt;/a&gt;, allowing this to be passed in (&lt;code&gt;docker build --build-arg DATABASE_URL=... ...&lt;/code&gt;).
&lt;em&gt;"Baking in"&lt;/em&gt; database credentials like this can be a serious security vulnerability - in this case, as it's only in an intermediate stage, it's not so risky, but it's still not a good practice.
We could provide more protection by treating it as a &lt;a href="https://docs.docker.com/build/building/secrets/"&gt;build secret&lt;/a&gt;, but ideally we want to have a single artifact we can test in and promote between different environments (I expand on the reasons for this &lt;a href="https://blog.jonrshar.pe/2020/Sep/19/spa-config.html"&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;So to force Next.js &lt;em&gt;not&lt;/em&gt; to pre-render API routes that need to hit the database for data, &lt;a href="https://nextjs.org/docs/14/app/building-your-application/routing/route-handlers#opting-out-of-caching"&gt;opt out of caching&lt;/a&gt;.
For example, add the &lt;a href="https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic"&gt;config&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt; import { PrismaClient } from &amp;quot;@prisma/client&amp;quot;;
&lt;span class="gi"&gt;+ &lt;/span&gt;
&lt;span class="gi"&gt;+ export const dynamic = &amp;quot;force-dynamic&amp;quot;;&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt; export async function GET() {
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now the build should work just fine.
In Node.js v15, the caching is opted out of &lt;a href="https://nextjs.org/blog/next-15#caching-semantics"&gt;by default&lt;/a&gt;, and you have to opt back in as needed.&lt;/p&gt;
&lt;h3&gt;Migrations&lt;/h3&gt;
&lt;p&gt;The Prisma files now need to be copied through to the final image so they're available at runtime for the migrations:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt; COPY --from=builder --chown=node /app/.next/static ./.next/static
&lt;span class="gi"&gt;+ COPY --from=builder --chown=node /app/prisma ./prisma&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt; COPY --from=builder --chown=node /app/public ./public

&lt;span class="w"&gt; &lt;/span&gt; COPY start.sh /usr/local/bin

&lt;span class="gi"&gt;+ ENV CHECKPOINT_DISABLE=1&lt;/span&gt;
&lt;span class="gi"&gt;+ ENV DISABLE_PRISMA_TELEMETRY=true&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt; ENV HOSTNAME=0.0.0.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;which we can add to the start script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt; set -ex
&lt;span class="gi"&gt;+  &lt;/span&gt;
&lt;span class="gi"&gt;+ npx --no-update-notifier prisma migrate deploy&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt; exec /sbin/tini -- node server.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But when we rebuild and &lt;em&gt;run&lt;/em&gt; the container, although it tries to apply the migrations, there are two problems:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;npm&lt;span class="w"&gt; &lt;/span&gt;warn&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;The&lt;span class="w"&gt; &lt;/span&gt;following&lt;span class="w"&gt; &lt;/span&gt;package&lt;span class="w"&gt; &lt;/span&gt;was&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;installed:&lt;span class="w"&gt; &lt;/span&gt;prisma@5.14.0
Prisma&lt;span class="w"&gt; &lt;/span&gt;schema&lt;span class="w"&gt; &lt;/span&gt;loaded&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;prisma/schema.prisma
Datasource&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;db&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;PostgreSQL&lt;span class="w"&gt; &lt;/span&gt;database

Error:&lt;span class="w"&gt; &lt;/span&gt;Prisma&lt;span class="w"&gt; &lt;/span&gt;schema&lt;span class="w"&gt; &lt;/span&gt;validation&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;get-config&lt;span class="w"&gt; &lt;/span&gt;wasm&lt;span class="o"&gt;)&lt;/span&gt;
Error&lt;span class="w"&gt; &lt;/span&gt;code:&lt;span class="w"&gt; &lt;/span&gt;P1012
error:&lt;span class="w"&gt; &lt;/span&gt;Environment&lt;span class="w"&gt; &lt;/span&gt;variable&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;found:&lt;span class="w"&gt; &lt;/span&gt;DATABASE_URL.
&lt;span class="w"&gt;  &lt;/span&gt;--&amp;gt;&lt;span class="w"&gt;  &lt;/span&gt;prisma/schema.prisma:13
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nv"&gt;provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;postgresql&amp;quot;&lt;/span&gt;
&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DATABASE_URL&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;

Validation&lt;span class="w"&gt; &lt;/span&gt;Error&lt;span class="w"&gt; &lt;/span&gt;Count:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;The first problem is that &lt;code&gt;npx&lt;/code&gt; is installing Prisma CLI, &lt;em&gt;at container start time&lt;/em&gt;. This both:&lt;ul&gt;
&lt;li&gt;Slows down starting the container; and&lt;/li&gt;
&lt;li&gt;Risks version drift between &lt;code&gt;@prisma/client&lt;/code&gt; and &lt;code&gt;prisma&lt;/code&gt;, which could cause hard-to-debug problems; and&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The second problem is that, like at build time, the database connection string isn't available.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To fix the first one, we want to install the right version of Prisma CLI &lt;em&gt;at build time&lt;/em&gt;.
As this dependency isn't used at runtime, the output tracing won't include it in &lt;code&gt;.next/standalone/node_modules&lt;/code&gt;, and the version in &lt;code&gt;package.json&lt;/code&gt; will generally be a semver range rather than specific.
But &lt;code&gt;@prisma/client&lt;/code&gt; &lt;em&gt;is&lt;/em&gt; included, and we know we want the same version, so we can look that up from the package file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt; COPY --from=builder --chown=node /app/public ./public
&lt;span class="gi"&gt;+ &lt;/span&gt;
&lt;span class="gi"&gt;+ RUN npm install --global --save-exact &amp;quot;prisma@$(node --print &amp;#39;require(&amp;quot;./node_modules/@prisma/client/package.json&amp;quot;).version&amp;#39;)&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt; COPY start.sh /usr/local/bin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can check that this does the right thing by rebuilding, then using the CLI as the container entrypoint:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--entrypoint&lt;span class="w"&gt; &lt;/span&gt;prisma&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;nextjs-image&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;version
prisma&lt;span class="w"&gt;                  &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.1.0
@prisma/client&lt;span class="w"&gt;          &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.1.0
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To fix the second problem, provide the connection string when starting the container, e.g.:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--env&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;DATABASE_URL=...&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;nextjs-image&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(&lt;strong&gt;Note&lt;/strong&gt; that if you want it to use a database on your local machine, you'll probably have to &lt;a href="https://docs.docker.com/desktop/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host"&gt;replace &lt;code&gt;localhost&lt;/code&gt;&lt;/a&gt; with &lt;code&gt;host.docker.internal&lt;/code&gt;.)&lt;/p&gt;
&lt;h2&gt;Run the container&lt;/h2&gt;
&lt;p&gt;You should now be able to run the final container, and expose it on your local network:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--env&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;DATABASE_URL=...&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--publish&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3000&lt;/span&gt;:3000&lt;span class="w"&gt; &lt;/span&gt;nextjs-image
+&lt;span class="w"&gt; &lt;/span&gt;npx&lt;span class="w"&gt; &lt;/span&gt;--no-update-notifier&lt;span class="w"&gt; &lt;/span&gt;prisma&lt;span class="w"&gt; &lt;/span&gt;migrate&lt;span class="w"&gt; &lt;/span&gt;deploy
Prisma&lt;span class="w"&gt; &lt;/span&gt;schema&lt;span class="w"&gt; &lt;/span&gt;loaded&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;prisma/schema.prisma
Datasource&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;db&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;PostgreSQL&lt;span class="w"&gt; &lt;/span&gt;database&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;prisma&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;schema&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;public&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;host.docker.internal:5432&amp;quot;&lt;/span&gt;

&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;migration&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;prisma/migrations


No&lt;span class="w"&gt; &lt;/span&gt;pending&lt;span class="w"&gt; &lt;/span&gt;migrations&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;apply.
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/sbin/tini&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;server.js
&lt;span class="w"&gt;   &lt;/span&gt;▲&lt;span class="w"&gt; &lt;/span&gt;Next.js&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;.1.2
&lt;span class="w"&gt;   &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Local:&lt;span class="w"&gt;        &lt;/span&gt;http://localhost:3000
&lt;span class="w"&gt;   &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Network:&lt;span class="w"&gt;      &lt;/span&gt;http://0.0.0.0:3000

&lt;span class="w"&gt; &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;Starting...
&lt;span class="w"&gt; &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;Ready&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;57ms
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Have a play with it - visit the home page, hit the &lt;code&gt;/api/users&lt;/code&gt; endpoint, and everything should work fine.
This is now ready for deployment to any container runtime!&lt;/p&gt;
&lt;h2&gt;More complex topologies&lt;/h2&gt;
&lt;p&gt;As mentioned above, this setup is for a fairly simple Next.js container; it can run happily as a single instance.
If you need high performance and availability, consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Serving the public and static asset directories from a static file server or CDN, rather than Node.js.&lt;/li&gt;
&lt;li&gt;Running migrations in a separate &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/"&gt;init container&lt;/a&gt;, rather than when the main container starts up.&lt;ul&gt;
&lt;li&gt;This reduces the possibility of race conditions around the migrations if you're running multiple instances.&lt;/li&gt;
&lt;li&gt;Installing the Prisma CLI seems to add substantially to the image size, so having a separate container for this would also lead to faster image pulls.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Ensuring you have appropriate logging - Next.js doesn't output anything after the startup message, in production mode.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Docker tools [Bonus]&lt;/h2&gt;
&lt;p&gt;If you're new to working with Docker containers, I'd recommend the following to allow you to explore in detail what's happening:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;During the &lt;code&gt;docker build&lt;/code&gt; process, you can see more of the logs by passing &lt;code&gt;--progress plain&lt;/code&gt; - this outputs everything into your terminal, rather than just scrolling through a few lines of each step and leaving the summary.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;docker run --entrypoint sh --interactive --tty &amp;lt;tag&amp;gt;&lt;/code&gt; (or replace &lt;code&gt;--interactive --tty&lt;/code&gt; with simply &lt;code&gt;-it&lt;/code&gt;) runs a simple shell inside your container, so you can explore what's there with e.g. &lt;code&gt;ls&lt;/code&gt; and &lt;code&gt;cat&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; that &lt;code&gt;sh&lt;/code&gt; on Alpine Linux is &lt;a href="https://en.wikipedia.org/wiki/Almquist_shell"&gt;&lt;code&gt;ash&lt;/code&gt;&lt;/a&gt; by default, rather than e.g. &lt;code&gt;bash&lt;/code&gt; (Ubuntu) or &lt;code&gt;zsh&lt;/code&gt; (macOS).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/wagoodman/dive"&gt;dive&lt;/a&gt; describes itself as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A tool for exploring a docker image, layer contents, and discovering ways to shrink the size of your Docker/OCI image.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It gives a very helpful text representation of each layer in your image, showing step-by-step where files are added.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content><category term="development"></category><category term="javascript"></category><category term="docker"></category><category term="ci"></category></entry><entry><title>Python TDD Ohm</title><link href="https://blog.jonrshar.pe/2024/Aug/17/python-tdd-ohm.html" rel="alternate"></link><published>2024-08-17T11:31:00+01:00</published><updated>2024-08-19T11:00:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2024-08-17:/2024/Aug/17/python-tdd-ohm.html</id><summary type="html">&lt;p&gt;Test-driven Python development done right - part 2&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: this was originally published as &lt;a href="https://blog.jonrshar.pe/2023/May/23/js-tdd-ohm.html"&gt;JS TDD Ohm&lt;/a&gt;, using JavaScript with Express and Jest, but I've recently been working with a client using &lt;a href="https://www.python.org/"&gt;Python&lt;/a&gt; with &lt;a href="https://docs.pytest.org/en/stable/"&gt;pytest&lt;/a&gt; and &lt;a href="https://fastapi.tiangolo.com/"&gt;FastAPI&lt;/a&gt;, so this article is an updated version of the same example using the latter tech stack.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In &lt;a href="https://blog.jonrshar.pe/2024/Aug/17/python-tdd-ftw.html"&gt;the previous article&lt;/a&gt; in this series, I introduced some of the basics of test-driven development (TDD):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;the process:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Red - write a failing test that describes the behaviour you want;&lt;/li&gt;
&lt;li&gt;Green - write the simplest possible code to make the test pass; and&lt;/li&gt;
&lt;li&gt;Refactor - clean up your code without breaking the tests.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the three main parts of a test:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Arrange&lt;/strong&gt; (sometimes known as &lt;em&gt;"given"&lt;/em&gt;) - set up the preconditions for our test...&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Act&lt;/strong&gt; (or &lt;em&gt;"when"&lt;/em&gt;) - do some work... This is what we're actually testing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assert&lt;/strong&gt; (or &lt;em&gt;"then"&lt;/em&gt;) - make sure that the work was done correctly.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;some of the benefits of test-driving implementations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;"...we can try out how we should interact with our code (its "interface") before we've even written any. We can have that discussion... while it's just a matter of changing our minds rather than the code."&lt;/em&gt;;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;"...it tells you when you're done. Once the tests are passing, the implementation meets the current requirements."&lt;/em&gt;; and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;"...we know that the code still does exactly what it's supposed to even [when] we've just changed the implementation. This allows us to confidently refactor towards cleaner code and higher quality."&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;how to &lt;em&gt;"call the shot"&lt;/em&gt; when running your tests:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;...make a prediction of what the test result will be, pass or fail. 
If you think the test will fail, &lt;strong&gt;why&lt;/strong&gt;; will the expectation be 
unmet (and what value do you think you'll get instead) or will 
something else go wrong?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this article we're going to dive into test-driving HTTP APIs and talk a bit more about how we can use testing to support us in designing the code we're working on.&lt;/p&gt;
&lt;h3&gt;Requirements&lt;/h3&gt;
&lt;p&gt;I've aimed this content at more junior developers, so there are more explanations than all readers will need, but anyone new to testing and TDD should find something to take from it. We'll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;*nix command line: already provided on macOS and Linux; if you're using Windows try WSL or Git BASH;&lt;/li&gt;
&lt;li&gt;Python (FastAPI requires at least 3.8 - this is written using 3.12, so you may need to write slightly different code in earlier versions, but all of the examples in the FastAPI docs allow you to pick your version) and &lt;a href="https://pipenv.pypa.io/en/latest/"&gt;pipenv&lt;/a&gt;; and&lt;/li&gt;
&lt;li&gt;Familiarity with Python syntax (including type annotations, which FastAPI uses to automatically generate API documentation).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition, given the domain for this post, you'll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Familiarity with HTTP requests and responses.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Again please carefully &lt;em&gt;read everything&lt;/em&gt;, and for newer developers I'd recommend &lt;em&gt;typing the code&lt;/em&gt; rather than copy-pasting.&lt;/p&gt;
&lt;h2&gt;Setting the scene [1/9]&lt;/h2&gt;
&lt;p&gt;Our customer, &lt;strong&gt;JonFX&lt;/strong&gt;, sells guitar pedal kits that you construct yourself at home.
These kits contain set of instructions and a bunch of electrical components, including resistors: &lt;/p&gt;
&lt;p&gt;&lt;img alt="Picture of some resistors" src="https://blog.jonrshar.pe/images/Electronic-Axial-Lead-Resistors-Array.png"&gt;&lt;/p&gt;
&lt;p&gt;There are three representations of resistance (measured in Ohms, Ω) in use within this ecosystem.
For example, given a 22,000Ω resistor, it can be represented as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A number, &lt;code&gt;22_000&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;A shorthand string, &lt;code&gt;"22K"&lt;/code&gt;; or&lt;/li&gt;
&lt;li&gt;A set of bands on the physical component, e.g. &lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: orange; background-color: black"&gt;&amp;nbsp;orange&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our customer has noted that people sometimes have difficulty converting between these representations, and asked us to build something to help solve the problem.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;How do we prioritise which representations we should focus on to start with?
We want to deliver the most valuable thing first, so let's do some analysis.
There are three &lt;em&gt;personas&lt;/em&gt; who work with these representations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Debbie the designer: Debbie designs the circuits, and generally works with the &lt;em&gt;number&lt;/em&gt; representation. Once a design is complete the values are recorded in a manifest using the &lt;em&gt;shorthand&lt;/em&gt; notation;&lt;/li&gt;
&lt;li&gt;Colin the customer: Colin wants to buy and build one of the kits, which will include the manifest and the components with their &lt;em&gt;bands&lt;/em&gt;; and&lt;/li&gt;
&lt;li&gt;Parul the packer: When Colin orders a kit, Parul is responsible for selecting the components based on the manifest, boxing them up and shipping them out.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Parul and Debbie both work with resistors and other electrical components on a very regular basis, so they probably don't need reminding what the bands mean, and if not there are various non-software interventions we could use to make their lives easier (for example, the boxes Parul is selecting components from could have a picture of the relevant bands and the shorthand printed in large letters to aid selection and refilling).
But it might be a while since Colin built his last kit (or he may even be a first-time customer), so that's the persona most likely to need help and therefore the highest value software would focus on the conversion between bands and shorthand, especially when you consider that the company will have far more Colins (thousands) than Paruls (ten) or Debbies (one).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Let's capture that as a &lt;em&gt;user story&lt;/em&gt; that we can refer back to if we need reminding what we're working towards:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;As a&lt;/strong&gt; customer&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I want&lt;/strong&gt; to convert a set of bands to a shorthand string&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So that&lt;/strong&gt; I can match a given resistor to the diagram&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For this exercise, we're going to be building the backend for a web UI; an acceptance criterion based on the above examples might be:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Given&lt;/strong&gt; an input of the bands red, red, orange&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When&lt;/strong&gt; the client makes a request&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then&lt;/strong&gt; the response contains the shorthand &lt;code&gt;"22K"&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: for the sake of simplicity we will be working on an implementation that can convert values from 10Ω (or 100Ω for three value bands) up to but not including 1,000,000,000Ω.&lt;/p&gt;
&lt;h2&gt;Welcome to the resistance [2/9]&lt;/h2&gt;
&lt;p&gt;As shown above, physical resistors have coloured bands which indicate their resistance.
The "rules of resistors" that we'll be following are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A resistor must have two or three &lt;em&gt;value&lt;/em&gt; bands, unless it's a 0Ω resistor (which must have only a single black value band);&lt;/li&gt;
&lt;li&gt;The first value band must not be black, unless it's a 0Ω resistor; and&lt;/li&gt;
&lt;li&gt;A resistor must have a single &lt;em&gt;multiplier&lt;/em&gt; band.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The band colours indicate numbers via the following mapping:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;6&lt;/th&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;th&gt;9&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: black"&gt;black&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: brown"&gt;brown&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: orange; background-color: black"&gt;&amp;nbsp;orange&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: yellow; background-color: black"&gt;&amp;nbsp;yellow&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: green"&gt;green&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: blue"&gt;blue&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: purple"&gt;violet&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: dimgrey"&gt;grey&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: white; background-color: black"&gt;&amp;nbsp;white&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The numerical resistance is determined by taking the two or three value bands as the first two or three digits, then adding the number of zeros specified by the multiplier band to the end, e.g.:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Value: &lt;strong&gt;&lt;span style="color: blue"&gt;blue&lt;/span&gt;&lt;/strong&gt; - 6&lt;/li&gt;
&lt;li&gt;Value: &lt;strong&gt;&lt;span style="color: dimgrey"&gt;grey&lt;/span&gt;&lt;/strong&gt; - 8&lt;/li&gt;
&lt;li&gt;Multiplier: &lt;strong&gt;&lt;span style="color: green"&gt;green&lt;/span&gt;&lt;/strong&gt; - 5&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;becomes 6,800,000Ω (6 then 8 then 5 zeros). You could also calculate this as &lt;code&gt;((6 * 10) + 8) * (10 ** 5)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The shorthand form is created by replacing the left-most comma with M (for "mega", meaning a factor of one million) and dropping all trailing zeros&lt;sup&gt;1&lt;/sup&gt;; in this case &lt;code&gt;"6M8"&lt;/code&gt;.
For values between 1,000Ω and 999,999Ω the comma is replaced with K (for "kilo", meaning a factor of one thousand) instead, hence the 22,000Ω above becomes &lt;code&gt;"22K"&lt;/code&gt;.
For values less than 1,000Ω the decimal point is replaced with R, so e.g. 150Ω (bands &lt;strong&gt;&lt;span style="color: brown"&gt;brown&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: green"&gt;green&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: brown"&gt;brown&lt;/span&gt;&lt;/strong&gt;) would be represented as &lt;code&gt;"150R"&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here are a few more examples, or for more details you can read about this &lt;a href="https://en.wikipedia.org/wiki/Electronic_color_code"&gt;electronic colour code&lt;/a&gt; on Wikipedia:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Numeric (Ω)&lt;/th&gt;
&lt;th&gt;Shorthand&lt;/th&gt;
&lt;th&gt;Bands&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"22R"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: black"&gt;black&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12,700&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"12K7"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: brown"&gt;brown&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: purple"&gt;violet&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;330,000&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"330K"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: orange; background-color: black"&gt;&amp;nbsp;orange&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: orange; background-color: black"&gt;&amp;nbsp;orange&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: black"&gt;black&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: orange; background-color: black"&gt;&amp;nbsp;orange&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8,200,000&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"8M2"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: dimgrey"&gt;grey&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: green"&gt;green&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;How can we represent this at the API level?
There are a few options, but for the purposes of working through this exercise let's say:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The request method will be &lt;code&gt;GET&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;The request path will be &lt;code&gt;/resistance&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;The bands will be provided as a query parameter named &lt;code&gt;bands&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;The response status code on success will be &lt;code&gt;200&lt;/code&gt; ("OK"); and&lt;/li&gt;
&lt;li&gt;The response body on success will be a JSON object containing the shorthand representation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using &lt;a href="https://en.wikipedia.org/wiki/CURL"&gt;cURL&lt;/a&gt;, this might look like (assuming an environment variable &lt;code&gt;URL&lt;/code&gt; has been set pointing to our API server):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="s2"&gt;/resistance?bands=brown&amp;amp;bands=red&amp;amp;bands=violet&amp;amp;bands=red&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shorthand&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;12K7&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;None more black [3/9]&lt;/h2&gt;
&lt;p&gt;Let's get started by creating a new &lt;a href="https://pipenv.pypa.io/en/latest/"&gt;pipenv&lt;/a&gt; package to hold our API:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;resistance
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$_&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;init
Reinitialized&lt;span class="w"&gt; &lt;/span&gt;existing&lt;span class="w"&gt; &lt;/span&gt;Git&lt;span class="w"&gt; &lt;/span&gt;repository&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance/.git/
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--allow-empty&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Initial commit&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;root-commit&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;7c30cd9&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Initial&lt;span class="w"&gt; &lt;/span&gt;commit
$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;install
Creating&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;Pipfile&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;project...
Pipfile.lock&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;found,&lt;span class="w"&gt; &lt;/span&gt;creating...
Locking&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;packages&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dependencies...
Locking&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;dev-packages&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dependencies...
Updated&lt;span class="w"&gt; &lt;/span&gt;Pipfile.lock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;702ad05de9bc9de99a4807c8dde1686f31e0041d7b5f6f6b74861195a52110f5&lt;span class="o"&gt;)&lt;/span&gt;!
To&lt;span class="w"&gt; &lt;/span&gt;activate&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;project&lt;span class="s1"&gt;&amp;#39;s virtualenv, run pipenv shell.&lt;/span&gt;
&lt;span class="s1"&gt;Alternatively, run a command inside the virtualenv with pipenv run.&lt;/span&gt;
&lt;span class="s1"&gt;To activate this project&amp;#39;&lt;/span&gt;s&lt;span class="w"&gt; &lt;/span&gt;virtualenv,&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;shell.
Alternatively,&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inside&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;virtualenv&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run.
Installing&lt;span class="w"&gt; &lt;/span&gt;dependencies&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;Pipfile.lock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;2110f5&lt;span class="o"&gt;)&lt;/span&gt;...
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Create pipenv project&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;c56f9f2&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Create&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;project
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertion&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will create a &lt;code&gt;Pipfile&lt;/code&gt;, containing something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[[source]]&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://pypi.org/simple&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;verify_ssl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pypi&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;[packages]&lt;/span&gt;

&lt;span class="k"&gt;[dev-packages]&lt;/span&gt;

&lt;span class="k"&gt;[requires]&lt;/span&gt;
&lt;span class="n"&gt;python_version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;3.12&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;along with a &lt;code&gt;Pipfile.lock&lt;/code&gt; which will be largely empty (until we start adding dependencies):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;_meta&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;hash&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;sha256&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;702ad05de9bc9de99a4807c8dde1686f31e0041d7b5f6f6b74861195a52110f5&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;pipfile-spec&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;requires&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;python_version&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;3.12&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;sources&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pypi&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://pypi.org/simple&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;verify_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;develop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, install &lt;a href="https://fastapi.tiangolo.com/"&gt;FastAPI&lt;/a&gt;, which we'll use to write and test our API endpoints, then &lt;a href="https://docs.pytest.org/en/stable/"&gt;pytest&lt;/a&gt; as the test runner:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;fastapi[standard]&amp;#39;&lt;/span&gt;
Installing&lt;span class="w"&gt; &lt;/span&gt;fastapi...
Resolving&lt;span class="w"&gt; &lt;/span&gt;fastapi&lt;span class="o"&gt;[&lt;/span&gt;standard&lt;span class="o"&gt;]&lt;/span&gt;...
Added&lt;span class="w"&gt; &lt;/span&gt;fastapi&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;Pipfile&lt;span class="s1"&gt;&amp;#39;s [packages] ...&lt;/span&gt;
&lt;span class="s1"&gt;✔ Installation Succeeded&lt;/span&gt;
&lt;span class="s1"&gt;Pipfile.lock (2110f5) out of date: run `pipfile lock` to update to (1a42bb)...&lt;/span&gt;
&lt;span class="s1"&gt;Running $ pipenv lock then $ pipenv sync.&lt;/span&gt;
&lt;span class="s1"&gt;Locking [packages] dependencies...&lt;/span&gt;
&lt;span class="s1"&gt;Building requirements...&lt;/span&gt;
&lt;span class="s1"&gt;Resolving dependencies...&lt;/span&gt;
&lt;span class="s1"&gt;✔ Success!&lt;/span&gt;
&lt;span class="s1"&gt;Locking [dev-packages] dependencies...&lt;/span&gt;
&lt;span class="s1"&gt;Updated Pipfile.lock (32f3a7325583c6d7bc3d4a81bbe168b8f4e158e2f313d4e85675c20d3d1a42bb)!&lt;/span&gt;
&lt;span class="s1"&gt;To activate this project&amp;#39;&lt;/span&gt;s&lt;span class="w"&gt; &lt;/span&gt;virtualenv,&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;shell.
Alternatively,&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inside&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;virtualenv&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run.
Installing&lt;span class="w"&gt; &lt;/span&gt;dependencies&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;Pipfile.lock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1a42bb&lt;span class="o"&gt;)&lt;/span&gt;...
All&lt;span class="w"&gt; &lt;/span&gt;dependencies&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;now&lt;span class="w"&gt; &lt;/span&gt;up-to-date!
To&lt;span class="w"&gt; &lt;/span&gt;activate&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;project&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;s&lt;span class="w"&gt; &lt;/span&gt;virtualenv,&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;shell.
Alternatively,&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inside&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;virtualenv&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run.
Installing&lt;span class="w"&gt; &lt;/span&gt;dependencies&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;Pipfile.lock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1a42bb&lt;span class="o"&gt;)&lt;/span&gt;...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--dev&lt;span class="w"&gt; &lt;/span&gt;pytest
Installing&lt;span class="w"&gt; &lt;/span&gt;pytest...
Resolving&lt;span class="w"&gt; &lt;/span&gt;pytest...
Added&lt;span class="w"&gt; &lt;/span&gt;pytest&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;Pipfile&lt;span class="s1"&gt;&amp;#39;s [dev-packages] ...&lt;/span&gt;
&lt;span class="s1"&gt;✔ Installation Succeeded&lt;/span&gt;
&lt;span class="s1"&gt;Pipfile.lock (1a42bb) out of date: run `pipfile lock` to update to (cef74e)...&lt;/span&gt;
&lt;span class="s1"&gt;Running $ pipenv lock then $ pipenv sync.&lt;/span&gt;
&lt;span class="s1"&gt;Locking [packages] dependencies...&lt;/span&gt;
&lt;span class="s1"&gt;Building requirements...&lt;/span&gt;
&lt;span class="s1"&gt;Resolving dependencies...&lt;/span&gt;
&lt;span class="s1"&gt;✔ Success!&lt;/span&gt;
&lt;span class="s1"&gt;Locking [dev-packages] dependencies...&lt;/span&gt;
&lt;span class="s1"&gt;Building requirements...&lt;/span&gt;
&lt;span class="s1"&gt;Resolving dependencies...&lt;/span&gt;
&lt;span class="s1"&gt;✔ Success!&lt;/span&gt;
&lt;span class="s1"&gt;Updated Pipfile.lock (9207f36ec8d8c7e488e13ad84852aa51d32c08e7f3ead19ec0c91e8930cef74e)!&lt;/span&gt;
&lt;span class="s1"&gt;To activate this project&amp;#39;&lt;/span&gt;s&lt;span class="w"&gt; &lt;/span&gt;virtualenv,&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;shell.
Alternatively,&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inside&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;virtualenv&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run.
Installing&lt;span class="w"&gt; &lt;/span&gt;dependencies&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;Pipfile.lock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;cef74e&lt;span class="o"&gt;)&lt;/span&gt;...
All&lt;span class="w"&gt; &lt;/span&gt;dependencies&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;now&lt;span class="w"&gt; &lt;/span&gt;up-to-date!
To&lt;span class="w"&gt; &lt;/span&gt;activate&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;project&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;s&lt;span class="w"&gt; &lt;/span&gt;virtualenv,&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;shell.
Alternatively,&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inside&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;virtualenv&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run.
Installing&lt;span class="w"&gt; &lt;/span&gt;dependencies&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;Pipfile.lock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;cef74e&lt;span class="o"&gt;)&lt;/span&gt;...
Installing&lt;span class="w"&gt; &lt;/span&gt;dependencies&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;Pipfile.lock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;cef74e&lt;span class="o"&gt;)&lt;/span&gt;...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will add those packages to your &lt;code&gt;Pipfile&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt; [packages]
&lt;span class="gi"&gt;+ fastapi = {extras = [&amp;quot;standard&amp;quot;], version = &amp;quot;*&amp;quot;}&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt; [dev-packages]
&lt;span class="gi"&gt;+ pytest = &amp;quot;*&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and update the lock file accordingly, as well as installing the packages for use locally.
Let's commit that:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Install dependencies&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;a3e239e&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Install&lt;span class="w"&gt; &lt;/span&gt;dependencies
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;715&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To make it easy to run the tests, add the following to the end of the &lt;code&gt;Pipfile&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gi"&gt;+ &lt;/span&gt;
&lt;span class="gi"&gt;+ [scripts]&lt;/span&gt;
&lt;span class="gi"&gt;+ test = &amp;quot;pytest&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now &lt;code&gt;pipenv run test&lt;/code&gt; will invoke pytest.
&lt;strong&gt;Call the shot&lt;/strong&gt;, then run that command.&lt;/p&gt;
&lt;hr&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;==================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===================================&lt;/span&gt;
platform&lt;span class="w"&gt; &lt;/span&gt;darwin&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.0,&lt;span class="w"&gt; &lt;/span&gt;pytest-8.3.2,&lt;span class="w"&gt; &lt;/span&gt;pluggy-1.5.0
rootdir:&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance
plugins:&lt;span class="w"&gt; &lt;/span&gt;anyio-4.4.0
collected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;

&lt;span class="o"&gt;=================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;no&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.00s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Hopefully this is what you predicted - the test script ran, pytest is correctly installed, but no tests were found.
So let's create one, starting with the &lt;strong&gt;simplest possible case&lt;/strong&gt;: the 0Ω resistor, a single black band. 
Put an empty &lt;code&gt;__init__.py&lt;/code&gt; in a &lt;code&gt;tests&lt;/code&gt; directory, then add the following to &lt;code&gt;tests/api_test.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HTTPStatus&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fastapi.testclient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestClient&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_single_black_band_returns_0R&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TestClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bands&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;HTTPStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OK&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shorthand&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0R&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Call the shot&lt;/strong&gt;, run the test.&lt;/p&gt;
&lt;hr&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;=======================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;========================================&lt;/span&gt;
platform&lt;span class="w"&gt; &lt;/span&gt;darwin&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.0,&lt;span class="w"&gt; &lt;/span&gt;pytest-8.3.2,&lt;span class="w"&gt; &lt;/span&gt;pluggy-1.5.0
rootdir:&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance
plugins:&lt;span class="w"&gt; &lt;/span&gt;anyio-4.4.0
collected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;items&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;

&lt;span class="o"&gt;==============================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ERRORS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==============================================&lt;/span&gt;
________________________________&lt;span class="w"&gt; &lt;/span&gt;ERROR&lt;span class="w"&gt; &lt;/span&gt;collecting&lt;span class="w"&gt; &lt;/span&gt;tests/api_test.py&lt;span class="w"&gt; &lt;/span&gt;________________________________
ImportError&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;importing&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;module&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;path/to/resistance/tests/api_test.py&amp;#39;&lt;/span&gt;.
Hint:&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;sure&lt;span class="w"&gt; &lt;/span&gt;your&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;modules/packages&lt;span class="w"&gt; &lt;/span&gt;have&lt;span class="w"&gt; &lt;/span&gt;valid&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;names.
Traceback:
/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/importlib/__init__.py:90:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;import_module
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;_bootstrap._gcd_import&lt;span class="o"&gt;(&lt;/span&gt;name&lt;span class="o"&gt;[&lt;/span&gt;level:&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;package,&lt;span class="w"&gt; &lt;/span&gt;level&lt;span class="o"&gt;)&lt;/span&gt;
tests/api_test.py:3:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;module&amp;gt;
&lt;span class="w"&gt;    &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;app&lt;span class="w"&gt; &lt;/span&gt;import&lt;span class="w"&gt; &lt;/span&gt;app
E&lt;span class="w"&gt;   &lt;/span&gt;ModuleNotFoundError:&lt;span class="w"&gt; &lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;module&lt;span class="w"&gt; &lt;/span&gt;named&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;app&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;=====================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;short&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;summary&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;======================================&lt;/span&gt;
ERROR&lt;span class="w"&gt; &lt;/span&gt;tests/api_test.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!&lt;span class="w"&gt; &lt;/span&gt;Interrupted:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;during&lt;span class="w"&gt; &lt;/span&gt;collection&lt;span class="w"&gt; &lt;/span&gt;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
&lt;span class="o"&gt;=========================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.35s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=========================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Hopefully you predicted that: &lt;code&gt;app&lt;/code&gt; wasn't defined, the test crashed before even getting the chance to fail.
So let's give it an app to test!
Create &lt;code&gt;app/__init__.py&lt;/code&gt; containing the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;What will happen when we re-run the test now?
&lt;strong&gt;Call the shot&lt;/strong&gt;, then run it again.&lt;/p&gt;
&lt;hr&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;=======================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;========================================&lt;/span&gt;
platform&lt;span class="w"&gt; &lt;/span&gt;darwin&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.0,&lt;span class="w"&gt; &lt;/span&gt;pytest-8.3.2,&lt;span class="w"&gt; &lt;/span&gt;pluggy-1.5.0
rootdir:&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance
plugins:&lt;span class="w"&gt; &lt;/span&gt;anyio-4.4.0
collected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;item

tests/api_test.py&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;                                                                          &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;=============================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FAILURES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=============================================&lt;/span&gt;
________________________________&lt;span class="w"&gt; &lt;/span&gt;test_single_black_band_returns_0R&lt;span class="w"&gt; &lt;/span&gt;_________________________________

&lt;span class="w"&gt;    &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;test_single_black_band_returns_0R&lt;span class="o"&gt;()&lt;/span&gt;:
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;TestClient&lt;span class="o"&gt;(&lt;/span&gt;app&lt;span class="o"&gt;)&lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dict&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;bands&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]))&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt;       &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;response.status_code&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;HTTPStatus.OK
E&lt;span class="w"&gt;       &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;404&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;HTTPStatus.OK:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&amp;gt;
E&lt;span class="w"&gt;        &lt;/span&gt;+&lt;span class="w"&gt;  &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;404&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;Response&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;404&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Not&lt;span class="w"&gt; &lt;/span&gt;Found&lt;span class="o"&gt;]&lt;/span&gt;&amp;gt;.status_code
E&lt;span class="w"&gt;        &lt;/span&gt;+&lt;span class="w"&gt;  &lt;/span&gt;and&lt;span class="w"&gt;   &lt;/span&gt;&amp;lt;HTTPStatus.OK:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;HTTPStatus.OK

tests/api_test.py:10:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;AssertionError&lt;/span&gt;
&lt;span class="o"&gt;=====================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;short&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;summary&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;======================================&lt;/span&gt;
FAILED&lt;span class="w"&gt; &lt;/span&gt;tests/api_test.py::test_single_black_band_returns_0R&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;404&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;HTTPStatus.OK:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&amp;gt;
&lt;span class="o"&gt;========================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.37s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=========================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's a bit more like it, the test is now &lt;em&gt;failing&lt;/em&gt; (rather than &lt;em&gt;crashing&lt;/em&gt;) and we're getting feedback at the HTTP API level (404 Not Found status code instead of the expected 200 OK).
Let's handle that endpoint and move the failure a bit further along; add the code to &lt;code&gt;app/__init__.py&lt;/code&gt; to handle the GET request and immediately return 200 OK:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt; from fastapi import FastAPI

&lt;span class="w"&gt; &lt;/span&gt; app = FastAPI()
&lt;span class="gi"&gt;+ &lt;/span&gt;
&lt;span class="gi"&gt;+ &lt;/span&gt;
&lt;span class="gi"&gt;+ @app.get(&amp;quot;/resistance&amp;quot;)&lt;/span&gt;
&lt;span class="gi"&gt;+ def _():&lt;/span&gt;
&lt;span class="gi"&gt;+     pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Call the shot&lt;/strong&gt;, then run the test.&lt;/p&gt;
&lt;hr&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;=======================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;========================================&lt;/span&gt;
platform&lt;span class="w"&gt; &lt;/span&gt;darwin&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.0,&lt;span class="w"&gt; &lt;/span&gt;pytest-8.3.2,&lt;span class="w"&gt; &lt;/span&gt;pluggy-1.5.0
rootdir:&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance
plugins:&lt;span class="w"&gt; &lt;/span&gt;anyio-4.4.0
collected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;item

tests/api_test.py&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;                                                                          &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;=============================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FAILURES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=============================================&lt;/span&gt;
________________________________&lt;span class="w"&gt; &lt;/span&gt;test_single_black_band_returns_0R&lt;span class="w"&gt; &lt;/span&gt;_________________________________

&lt;span class="w"&gt;    &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;test_single_black_band_returns_0R&lt;span class="o"&gt;()&lt;/span&gt;:
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;TestClient&lt;span class="o"&gt;(&lt;/span&gt;app&lt;span class="o"&gt;)&lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dict&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;bands&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]))&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;response.status_code&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;HTTPStatus.OK
&amp;gt;&lt;span class="w"&gt;       &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;response.json&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shorthand&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0R&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
E&lt;span class="w"&gt;       &lt;/span&gt;AssertionError:&lt;span class="w"&gt; &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;None&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;shorthand&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;0R&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
E&lt;span class="w"&gt;        &lt;/span&gt;+&lt;span class="w"&gt;  &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;None&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;json&lt;span class="o"&gt;()&lt;/span&gt;
E&lt;span class="w"&gt;        &lt;/span&gt;+&lt;span class="w"&gt;    &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;Response&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;OK&lt;span class="o"&gt;]&lt;/span&gt;&amp;gt;.json

tests/api_test.py:9:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;AssertionError&lt;/span&gt;
&lt;span class="o"&gt;=====================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;short&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;summary&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;======================================&lt;/span&gt;
FAILED&lt;span class="w"&gt; &lt;/span&gt;tests/api_test.py::test_single_black_band_returns_0R&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;AssertionError:&lt;span class="w"&gt; &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;None&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;shorthand&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;0R&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;========================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.36s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=========================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we see a failure for the body of the response, rather than the status code.
If not, you may be handling the wrong path or method; double-check that the code in &lt;code&gt;app/__init__.py&lt;/code&gt; matches up with the request defined in &lt;code&gt;tests/api_test.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This makes sense - our &lt;em&gt;"path operation function"&lt;/em&gt; returns &lt;code&gt;None&lt;/code&gt;, so the response content JSON will be &lt;code&gt;null&lt;/code&gt;.
One of FastAPI's features is the use of &lt;a href="https://fastapi.tiangolo.com/tutorial/response-model/"&gt;models&lt;/a&gt; to document (in the code itself and in generated &lt;a href="https://www.openapis.org/"&gt;OpenAPI&lt;/a&gt; documentation) request and response bodies.
Add a model describing the type of response body we're expecting to &lt;code&gt;app/__init__.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt; from fastapi import FastAPI
&lt;span class="gi"&gt;+ from pydantic import BaseModel&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt; app = FastAPI()
&lt;span class="gi"&gt;+ &lt;/span&gt;
&lt;span class="gi"&gt;+ &lt;/span&gt;
&lt;span class="gi"&gt;+ class ResistanceModel(BaseModel):&lt;/span&gt;
&lt;span class="gi"&gt;+     shorthand: str&lt;/span&gt;


&lt;span class="w"&gt; &lt;/span&gt; @app.get(&amp;quot;/resistance&amp;quot;)
&lt;span class="gd"&gt;- def _():&lt;/span&gt;
&lt;span class="gi"&gt;+ def _() -&amp;gt; ResistanceModel:&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;     pass
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Call the shot&lt;/strong&gt;, then run the test.&lt;/p&gt;
&lt;hr&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;=======================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;========================================&lt;/span&gt;
platform&lt;span class="w"&gt; &lt;/span&gt;darwin&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.0,&lt;span class="w"&gt; &lt;/span&gt;pytest-8.3.2,&lt;span class="w"&gt; &lt;/span&gt;pluggy-1.5.0
rootdir:&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance
plugins:&lt;span class="w"&gt; &lt;/span&gt;anyio-4.4.0
collected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;item

tests/api_test.py&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;                                                                          &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;=============================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FAILURES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=============================================&lt;/span&gt;
________________________________&lt;span class="w"&gt; &lt;/span&gt;test_single_black_band_returns_0R&lt;span class="w"&gt; &lt;/span&gt;_________________________________

&lt;span class="w"&gt;    &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;test_single_black_band_returns_0R&lt;span class="o"&gt;()&lt;/span&gt;:
&amp;gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;TestClient&lt;span class="o"&gt;(&lt;/span&gt;app&lt;span class="o"&gt;)&lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dict&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;bands&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]))&lt;/span&gt;

tests/api_test.py:9:
_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_&lt;span class="w"&gt; &lt;/span&gt;_
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;errors:
&amp;gt;&lt;span class="w"&gt;               &lt;/span&gt;raise&lt;span class="w"&gt; &lt;/span&gt;ResponseValidationError&lt;span class="o"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nv"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;_normalize_errors&lt;span class="o"&gt;(&lt;/span&gt;errors&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;response_content
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
E&lt;span class="w"&gt;               &lt;/span&gt;fastapi.exceptions.ResponseValidationError:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;validation&lt;span class="w"&gt; &lt;/span&gt;errors:
E&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;model_attributes_type&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;loc&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;response&amp;#39;&lt;/span&gt;,&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;msg&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Input should be a valid dictionary or object to extract fields from&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;input&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;None&lt;span class="o"&gt;}&lt;/span&gt;

../../../../.local/share/virtualenvs/resistance-UW3A4gHD/lib/python3.12/site-packages/fastapi/routing.py:155:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ResponseValidationError&lt;/span&gt;
&lt;span class="o"&gt;=====================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;short&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;summary&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;======================================&lt;/span&gt;
FAILED&lt;span class="w"&gt; &lt;/span&gt;tests/api_test.py::test_single_black_band_returns_0R&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;fastapi.exceptions.ResponseValidationError:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;validation&lt;span class="w"&gt; &lt;/span&gt;errors:
&lt;span class="o"&gt;========================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.18s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=========================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This one might be a bit surprising.
Rather than seeing some kind of response at the HTTP API level, the test is actually receiving a &lt;code&gt;ResponseValidationError&lt;/code&gt; at the Python level.
Is this what would happen in real life, would our server crash and error without responding? 
Let's add a new script in the &lt;code&gt;Pipfile&lt;/code&gt;, to allow us to start up the application, and investigate what actually happens:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt; [scripts]
&lt;span class="gi"&gt;+ dev = &amp;quot;fastapi dev app&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt; test = &amp;quot;pytest&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;dev
INFO&lt;span class="w"&gt;     &lt;/span&gt;Using&lt;span class="w"&gt; &lt;/span&gt;path&lt;span class="w"&gt; &lt;/span&gt;app
INFO&lt;span class="w"&gt;     &lt;/span&gt;Resolved&lt;span class="w"&gt; &lt;/span&gt;absolute&lt;span class="w"&gt; &lt;/span&gt;path&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance/app
INFO&lt;span class="w"&gt;     &lt;/span&gt;Searching&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;package&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;structure&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;directories&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;__init__.py&lt;span class="w"&gt; &lt;/span&gt;files
INFO&lt;span class="w"&gt;     &lt;/span&gt;Importing&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance

&lt;span class="w"&gt; &lt;/span&gt;╭─&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;package&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;structure&lt;span class="w"&gt; &lt;/span&gt;─╮
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;                                 &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;📁&lt;span class="w"&gt; &lt;/span&gt;app&lt;span class="w"&gt;                         &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;🐍&lt;span class="w"&gt; &lt;/span&gt;__init__.py&lt;span class="w"&gt;             &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;                                 &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;╰─────────────────────────────────╯

INFO&lt;span class="w"&gt;     &lt;/span&gt;Importing&lt;span class="w"&gt; &lt;/span&gt;module&lt;span class="w"&gt; &lt;/span&gt;app
INFO&lt;span class="w"&gt;     &lt;/span&gt;Found&lt;span class="w"&gt; &lt;/span&gt;importable&lt;span class="w"&gt; &lt;/span&gt;FastAPI&lt;span class="w"&gt; &lt;/span&gt;app

&lt;span class="w"&gt; &lt;/span&gt;╭─&lt;span class="w"&gt; &lt;/span&gt;Importable&lt;span class="w"&gt; &lt;/span&gt;FastAPI&lt;span class="w"&gt; &lt;/span&gt;app&lt;span class="w"&gt; &lt;/span&gt;─╮
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;                          &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;app&lt;span class="w"&gt; &lt;/span&gt;import&lt;span class="w"&gt; &lt;/span&gt;app&lt;span class="w"&gt;     &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;                          &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;╰──────────────────────────╯

INFO&lt;span class="w"&gt;     &lt;/span&gt;Using&lt;span class="w"&gt; &lt;/span&gt;import&lt;span class="w"&gt; &lt;/span&gt;string&lt;span class="w"&gt; &lt;/span&gt;app:app

&lt;span class="w"&gt; &lt;/span&gt;╭──────────&lt;span class="w"&gt; &lt;/span&gt;FastAPI&lt;span class="w"&gt; &lt;/span&gt;CLI&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Development&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;───────────╮
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;                                                     &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;Serving&lt;span class="w"&gt; &lt;/span&gt;at:&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:8000&lt;span class="w"&gt;                  &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;                                                     &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;API&lt;span class="w"&gt; &lt;/span&gt;docs:&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:8000/docs&lt;span class="w"&gt;               &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;                                                     &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;Running&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;development&lt;span class="w"&gt; &lt;/span&gt;mode,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;production&lt;span class="w"&gt; &lt;/span&gt;use:&lt;span class="w"&gt;   &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;                                                     &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;fastapi&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt;                                        &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;                                                     &lt;/span&gt;│
&lt;span class="w"&gt; &lt;/span&gt;╰─────────────────────────────────────────────────────╯

INFO:&lt;span class="w"&gt;     &lt;/span&gt;Will&lt;span class="w"&gt; &lt;/span&gt;watch&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;changes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;these&lt;span class="w"&gt; &lt;/span&gt;directories:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;path/to/resistance&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Uvicorn&lt;span class="w"&gt; &lt;/span&gt;running&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:8000&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Press&lt;span class="w"&gt; &lt;/span&gt;CTRL+C&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;quit&lt;span class="o"&gt;)&lt;/span&gt;
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Started&lt;span class="w"&gt; &lt;/span&gt;reloader&lt;span class="w"&gt; &lt;/span&gt;process&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;64850&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;WatchFiles
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Started&lt;span class="w"&gt; &lt;/span&gt;server&lt;span class="w"&gt; &lt;/span&gt;process&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;64863&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Waiting&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;application&lt;span class="w"&gt; &lt;/span&gt;startup.
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Application&lt;span class="w"&gt; &lt;/span&gt;startup&lt;span class="w"&gt; &lt;/span&gt;complete.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In another terminal session, run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:8000/resistance?bands&lt;span class="o"&gt;=&lt;/span&gt;black
*&lt;span class="w"&gt;   &lt;/span&gt;Trying&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;127&lt;/span&gt;.0.0.1:8000...
*&lt;span class="w"&gt; &lt;/span&gt;Connected&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;127&lt;/span&gt;.0.0.1&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;127&lt;/span&gt;.0.0.1&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8000&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;GET&lt;span class="w"&gt; &lt;/span&gt;/resistance?bands&lt;span class="o"&gt;=&lt;/span&gt;black&lt;span class="w"&gt; &lt;/span&gt;HTTP/1.1
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Host:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;127&lt;/span&gt;.0.0.1:8000
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;User-Agent:&lt;span class="w"&gt; &lt;/span&gt;curl/8.7.1
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Accept:&lt;span class="w"&gt; &lt;/span&gt;*/*
&amp;gt;
*&lt;span class="w"&gt; &lt;/span&gt;Request&lt;span class="w"&gt; &lt;/span&gt;completely&lt;span class="w"&gt; &lt;/span&gt;sent&lt;span class="w"&gt; &lt;/span&gt;off
&amp;lt;&lt;span class="w"&gt; &lt;/span&gt;HTTP/1.1&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Internal&lt;span class="w"&gt; &lt;/span&gt;Server&lt;span class="w"&gt; &lt;/span&gt;Error
&amp;lt;&lt;span class="w"&gt; &lt;/span&gt;date:&lt;span class="w"&gt; &lt;/span&gt;Thu,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Aug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;:00:40&lt;span class="w"&gt; &lt;/span&gt;GMT
&amp;lt;&lt;span class="w"&gt; &lt;/span&gt;server:&lt;span class="w"&gt; &lt;/span&gt;uvicorn
&amp;lt;&lt;span class="w"&gt; &lt;/span&gt;content-length:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;
&amp;lt;&lt;span class="w"&gt; &lt;/span&gt;content-type:&lt;span class="w"&gt; &lt;/span&gt;text/plain&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;utf-8
&amp;lt;
*&lt;span class="w"&gt; &lt;/span&gt;Connection&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#0 to host 127.0.0.1 left intact&lt;/span&gt;
Internal&lt;span class="w"&gt; &lt;/span&gt;Server&lt;span class="w"&gt; &lt;/span&gt;Error
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is the expected behaviour - the server still sends a response, but with a 5xx (server-side) error.
Back in the FastAPI logs, we see the details:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;INFO:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;127&lt;/span&gt;.0.0.1:56075&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GET /resistance?bands=black HTTP/1.1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Internal&lt;span class="w"&gt; &lt;/span&gt;Server&lt;span class="w"&gt; &lt;/span&gt;Error
ERROR:&lt;span class="w"&gt;    &lt;/span&gt;Exception&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ASGI&lt;span class="w"&gt; &lt;/span&gt;application
Traceback&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
fastapi.exceptions.ResponseValidationError:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;validation&lt;span class="w"&gt; &lt;/span&gt;errors:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;model_attributes_type&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;loc&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;response&amp;#39;&lt;/span&gt;,&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;msg&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Input should be a valid dictionary or object to extract fields from&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;input&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;None&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We do have a failing test, and could continue to get it passing, but the &lt;em&gt;diagnostics&lt;/em&gt; are important.
We want to be able to test behaviour (HTTP requests and responses) not implementation details (Python errors).
So let's use a powerful pytest feature, &lt;a href="https://docs.pytest.org/en/stable/explanation/fixtures.html"&gt;fixtures&lt;/a&gt;, to solve the problem without filling our tests with details of what's happening.
Create a &lt;code&gt;tests/conftest.py&lt;/code&gt; containing the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;collections.abc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Generator&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;socket&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;threading&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Thread&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;httpx&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;uvicorn&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;


&lt;span class="nd"&gt;@pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;module&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Generator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;TestServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random_port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;random_port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TestServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;socket_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;socket_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket_&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sockets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_socket&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__enter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TestServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__exit__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_tb&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;should_exit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getsockname&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you want the detailed explanation of what's happening here, see the &lt;a href="#bonus"&gt;bonus section&lt;/a&gt;.
In brief, though, it's starting the app as a web server in the background, then providing a client the tests can use to make requests to it.&lt;/p&gt;
&lt;p&gt;Update &lt;code&gt;tests/api_test.py&lt;/code&gt; to use the fixture, instead of making its own FastAPI &lt;code&gt;TestClient&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt; from http import HTTPStatus

&lt;span class="gd"&gt;- from fastapi.testclient import TestClient&lt;/span&gt;
&lt;span class="gi"&gt;+ from httpx import Client&lt;/span&gt;
&lt;span class="gd"&gt;- &lt;/span&gt;
&lt;span class="gd"&gt;- from app import app&lt;/span&gt;


&lt;span class="gd"&gt;- def test_single_black_band_returns_0R():&lt;/span&gt;
&lt;span class="gd"&gt;-     response = TestClient(app).get(&amp;quot;/resistance&amp;quot;, params=dict(bands=[&amp;quot;black&amp;quot;]))&lt;/span&gt;
&lt;span class="gi"&gt;+ def test_single_black_band_returns_0R(client: Client):&lt;/span&gt;
&lt;span class="gi"&gt;+     response = client.get(&amp;quot;/resistance&amp;quot;, params=dict(bands=[&amp;quot;black&amp;quot;]))&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;     assert response.status_code == HTTPStatus.OK
&lt;span class="w"&gt; &lt;/span&gt;     assert response.json() == {&amp;quot;shorthand&amp;quot;: &amp;quot;0R&amp;quot;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now run the test again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;=======================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;========================================&lt;/span&gt;
platform&lt;span class="w"&gt; &lt;/span&gt;darwin&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.0,&lt;span class="w"&gt; &lt;/span&gt;pytest-8.3.2,&lt;span class="w"&gt; &lt;/span&gt;pluggy-1.5.0
rootdir:&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance
plugins:&lt;span class="w"&gt; &lt;/span&gt;anyio-4.4.0
collected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;item

tests/api_test.py&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;                                                                          &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;=============================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FAILURES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=============================================&lt;/span&gt;
________________________________&lt;span class="w"&gt; &lt;/span&gt;test_single_black_band_returns_0R&lt;span class="w"&gt; &lt;/span&gt;_________________________________

&lt;span class="nv"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;httpx.Client&lt;span class="w"&gt; &lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;0x104f4e570&amp;gt;

&lt;span class="w"&gt;    &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;test_single_black_band_returns_0R&lt;span class="o"&gt;(&lt;/span&gt;client:&lt;span class="w"&gt; &lt;/span&gt;Client&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;client.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dict&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;bands&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]))&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt;       &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;response.status_code&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;HTTPStatus.OK
E&lt;span class="w"&gt;       &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;HTTPStatus.OK:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&amp;gt;
E&lt;span class="w"&gt;        &lt;/span&gt;+&lt;span class="w"&gt;  &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;Response&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Internal&lt;span class="w"&gt; &lt;/span&gt;Server&lt;span class="w"&gt; &lt;/span&gt;Error&lt;span class="o"&gt;]&lt;/span&gt;&amp;gt;.status_code
E&lt;span class="w"&gt;        &lt;/span&gt;+&lt;span class="w"&gt;  &lt;/span&gt;and&lt;span class="w"&gt;   &lt;/span&gt;&amp;lt;HTTPStatus.OK:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;HTTPStatus.OK

tests/api_test.py:8:&lt;span class="w"&gt; &lt;/span&gt;AssertionError
--------------------------------------&lt;span class="w"&gt; &lt;/span&gt;Captured&lt;span class="w"&gt; &lt;/span&gt;stderr&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;---------------------------------------
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Started&lt;span class="w"&gt; &lt;/span&gt;server&lt;span class="w"&gt; &lt;/span&gt;process&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;72231&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Waiting&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;application&lt;span class="w"&gt; &lt;/span&gt;startup.
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Application&lt;span class="w"&gt; &lt;/span&gt;startup&lt;span class="w"&gt; &lt;/span&gt;complete.
---------------------------------------&lt;span class="w"&gt; &lt;/span&gt;Captured&lt;span class="w"&gt; &lt;/span&gt;stdout&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;---------------------------------------
INFO:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;127&lt;/span&gt;.0.0.1:57542&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GET /resistance?bands=black HTTP/1.1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Internal&lt;span class="w"&gt; &lt;/span&gt;Server&lt;span class="w"&gt; &lt;/span&gt;Error
---------------------------------------&lt;span class="w"&gt; &lt;/span&gt;Captured&lt;span class="w"&gt; &lt;/span&gt;stderr&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;---------------------------------------
ERROR:&lt;span class="w"&gt;    &lt;/span&gt;Exception&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ASGI&lt;span class="w"&gt; &lt;/span&gt;application
Traceback&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;raise&lt;span class="w"&gt; &lt;/span&gt;ResponseValidationError&lt;span class="o"&gt;(&lt;/span&gt;
fastapi.exceptions.ResponseValidationError:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;validation&lt;span class="w"&gt; &lt;/span&gt;errors:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;model_attributes_type&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;loc&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;response&amp;#39;&lt;/span&gt;,&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;msg&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Input should be a valid dictionary or object to extract fields from&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;input&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;None&lt;span class="o"&gt;}&lt;/span&gt;

-------------------------------------&lt;span class="w"&gt; &lt;/span&gt;Captured&lt;span class="w"&gt; &lt;/span&gt;stderr&lt;span class="w"&gt; &lt;/span&gt;teardown&lt;span class="w"&gt; &lt;/span&gt;-------------------------------------
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Shutting&lt;span class="w"&gt; &lt;/span&gt;down
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Waiting&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;application&lt;span class="w"&gt; &lt;/span&gt;shutdown.
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Application&lt;span class="w"&gt; &lt;/span&gt;shutdown&lt;span class="w"&gt; &lt;/span&gt;complete.
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Finished&lt;span class="w"&gt; &lt;/span&gt;server&lt;span class="w"&gt; &lt;/span&gt;process&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;72231&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;=====================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;short&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;summary&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;======================================&lt;/span&gt;
FAILED&lt;span class="w"&gt; &lt;/span&gt;tests/api_test.py::test_single_black_band_returns_0R&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;HTTPStatus.OK:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&amp;gt;
&lt;span class="o"&gt;========================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.27s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=========================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can still see the details of &lt;em&gt;why&lt;/em&gt; the server responded 500, but now the actual failure is the status code mismatch rather than a low-level error.
This is exactly what we're looking for, so update the implementation to get the test passing, then make a commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;=======================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;========================================&lt;/span&gt;
platform&lt;span class="w"&gt; &lt;/span&gt;darwin&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.0,&lt;span class="w"&gt; &lt;/span&gt;pytest-8.3.2,&lt;span class="w"&gt; &lt;/span&gt;pluggy-1.5.0
rootdir:&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance
plugins:&lt;span class="w"&gt; &lt;/span&gt;anyio-4.4.0
collected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;item

tests/api_test.py&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt;                                                                          &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;========================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.36s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=========================================&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Implement 0 Ohm resistor&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;e062a7b&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Implement&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Ohm&lt;span class="w"&gt; &lt;/span&gt;resistor
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;55&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;app/__init__.py
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests/__init__.py
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests/api_test.py
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests/conftest.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Unhappy path to design [4/9]&lt;/h2&gt;
&lt;p&gt;At this point you might be tempted to jump straight to an example like the 22kΩ resistor in the introduction, writing something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_red_red_orange_returns_22K&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bands&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;red&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;red&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;orange&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;HTTPStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OK&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shorthand&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;22K&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But bear in mind that this is an HTTP API.
Anyone can make a request to it, and they might not send one that's well-formed.
In this case, where it's expecting a request like &lt;code&gt;/resistance?bands=black&lt;/code&gt;, what if there &lt;em&gt;isn't&lt;/em&gt; a query parameter?
I've found this &lt;a href="https://www.codetinkerer.com/2015/12/04/choosing-an-http-status-code.html"&gt;status code flowchart&lt;/a&gt; really useful for figuring out a semantically appropriate response; working through that I get down to &lt;code&gt;400 Bad Request&lt;/code&gt;.
So let's write &lt;em&gt;that&lt;/em&gt; test:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_no_bands_responds_400&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;HTTPStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BAD_REQUEST&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Follow the TDD process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Call the shot;&lt;/li&gt;
&lt;li&gt;Run the test;&lt;/li&gt;
&lt;li&gt;Ensure it fails usefully (edit the test and repeat steps 1 and 2 as needed);&lt;/li&gt;
&lt;li&gt;Get it passing (edit the implementation); and&lt;/li&gt;
&lt;li&gt;Make a commit.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Remember&lt;/strong&gt;: never rely on your clients to make valid requests.
Even if you only intend for the API to be consumed by e.g. a React app you're maintaining, always check that input validation and authentication is applied correctly; it's trivial to make a request &lt;em&gt;without&lt;/em&gt; using the UI.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Next, what if there is a &lt;code&gt;bands&lt;/code&gt; query parameter but its value isn't &lt;code&gt;black&lt;/code&gt;?
That's a &lt;em&gt;structurally&lt;/em&gt; valid request, it has the query parameter, but e.g. &lt;code&gt;/resistance?bands=blue&lt;/code&gt; is &lt;em&gt;semantically&lt;/em&gt; invalid; there's no real resistor with a single blue band.
From the above flowchart, I get to &lt;code&gt;422 Unprocessable Entity&lt;/code&gt;.
So let's write a test for that:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_single_blue_band_responds_422&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bands&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;HTTPStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UNPROCESSABLE_ENTITY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Call the shot&lt;/strong&gt;, run the test.&lt;/p&gt;
&lt;hr&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;$pipenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;==================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===================================&lt;/span&gt;
platform&lt;span class="w"&gt; &lt;/span&gt;darwin&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.0,&lt;span class="w"&gt; &lt;/span&gt;pytest-8.3.2,&lt;span class="w"&gt; &lt;/span&gt;pluggy-1.5.0
rootdir:&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance
plugins:&lt;span class="w"&gt; &lt;/span&gt;anyio-4.4.0
collected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;items

tests/api_test.py&lt;span class="w"&gt; &lt;/span&gt;..F&lt;span class="w"&gt;                                                              &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;========================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FAILURES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;========================================&lt;/span&gt;
___________________________&lt;span class="w"&gt; &lt;/span&gt;test_single_blue_band_responds_422&lt;span class="w"&gt; &lt;/span&gt;___________________________

&lt;span class="nv"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;httpx.Client&lt;span class="w"&gt; &lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;0x11165aa50&amp;gt;

&lt;span class="w"&gt;    &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;test_single_blue_band_responds_422&lt;span class="o"&gt;(&lt;/span&gt;client:&lt;span class="w"&gt; &lt;/span&gt;Client&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;client.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dict&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;bands&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]))&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt;       &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;response.status_code&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;HTTPStatus.UNPROCESSABLE_ENTITY
E&lt;span class="w"&gt;       &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;HTTPStatus.UNPROCESSABLE_ENTITY:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;&amp;gt;
E&lt;span class="w"&gt;        &lt;/span&gt;+&lt;span class="w"&gt;  &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;Response&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;OK&lt;span class="o"&gt;]&lt;/span&gt;&amp;gt;.status_code
E&lt;span class="w"&gt;        &lt;/span&gt;+&lt;span class="w"&gt;  &lt;/span&gt;and&lt;span class="w"&gt;   &lt;/span&gt;&amp;lt;HTTPStatus.UNPROCESSABLE_ENTITY:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;HTTPStatus.UNPROCESSABLE_ENTITY

tests/api_test.py:19:&lt;span class="w"&gt; &lt;/span&gt;AssertionError
----------------------------------&lt;span class="w"&gt; &lt;/span&gt;Captured&lt;span class="w"&gt; &lt;/span&gt;stdout&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;----------------------------------
INFO:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;127&lt;/span&gt;.0.0.1:62543&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GET /resistance?bands=blue HTTP/1.1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;OK
--------------------------------&lt;span class="w"&gt; &lt;/span&gt;Captured&lt;span class="w"&gt; &lt;/span&gt;stderr&lt;span class="w"&gt; &lt;/span&gt;teardown&lt;span class="w"&gt; &lt;/span&gt;--------------------------------
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Shutting&lt;span class="w"&gt; &lt;/span&gt;down
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Waiting&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;application&lt;span class="w"&gt; &lt;/span&gt;shutdown.
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Application&lt;span class="w"&gt; &lt;/span&gt;shutdown&lt;span class="w"&gt; &lt;/span&gt;complete.
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Finished&lt;span class="w"&gt; &lt;/span&gt;server&lt;span class="w"&gt; &lt;/span&gt;process&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;12134&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;short&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;summary&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=================================&lt;/span&gt;
FAILED&lt;span class="w"&gt; &lt;/span&gt;tests/api_test.py::test_single_blue_band_responds_422&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;HTTPStatus.UNPROCESSABLE_ENTITY:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;&amp;gt;
&lt;span class="o"&gt;==============================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.27s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===============================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Depending on which way you implemented the previous step, you might see either 200 != 422 or 400 != 422.
Either way, that's not the status code we're expecting.
The temptation here might be to do something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Annotated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ResistanceModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bands&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HTTPStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bands&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HTTPStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UNPROCESSABLE_ENTITY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ResistanceModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shorthand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0R&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;However, this is mixing up two very important concepts.
We have two &lt;em&gt;domains&lt;/em&gt; here: &lt;strong&gt;transport&lt;/strong&gt; (HTTP requests and responses, things like paths, query parameters and status codes); and &lt;strong&gt;business&lt;/strong&gt; (resistors and their resistance values).
Splitting this out into those two domains might look like:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request&lt;/th&gt;
&lt;th&gt;Transport&lt;/th&gt;
&lt;th&gt;Business&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /resistance&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;"A request with no &lt;code&gt;bands&lt;/code&gt; query parameter is bad."&lt;/em&gt; -&amp;gt; &lt;code&gt;400&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /resistance?bands=blue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;"An invalid resistor isn't processable."&lt;/em&gt; -&amp;gt; &lt;code&gt;422&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;"A resistor with a single blue band isn't valid."&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Here you can see the split described above - the left-hand side is about HTTP APIs, the right-hand side is about resistors.
While handling a &lt;em&gt;structurally&lt;/em&gt; invalid request can be done entirely at the transport level, handling a &lt;em&gt;semantically&lt;/em&gt; invalid request is a business level question.&lt;/p&gt;
&lt;p&gt;So let's take this opportunity to split out a &lt;em&gt;service&lt;/em&gt; in &lt;code&gt;app/service.py&lt;/code&gt; to handle the business domain:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0R&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and use that in the &lt;code&gt;app/__init__.py&lt;/code&gt; to create the &lt;code&gt;shorthand&lt;/code&gt; attribute in the &lt;code&gt;ResistanceModel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is a simple &lt;em&gt;refactor&lt;/em&gt;, the &lt;code&gt;200&lt;/code&gt; and &lt;code&gt;400&lt;/code&gt; tests should still pass, and the &lt;code&gt;422&lt;/code&gt; test should still fail (you can comment it out or &lt;a href="https://docs.pytest.org/en/stable/how-to/skipping.html"&gt;skip it&lt;/a&gt; to double-check).
It also gives us a new &lt;em&gt;boundary&lt;/em&gt; to test at, we can exercise the service code directly in &lt;code&gt;tests/service_test.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;resistance&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_single_black_band_returns_0R&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0R&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At this point everything should be passing except the new API test:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;==================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===================================&lt;/span&gt;
platform&lt;span class="w"&gt; &lt;/span&gt;darwin&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.0,&lt;span class="w"&gt; &lt;/span&gt;pytest-8.3.2,&lt;span class="w"&gt; &lt;/span&gt;pluggy-1.5.0
rootdir:&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance
plugins:&lt;span class="w"&gt; &lt;/span&gt;anyio-4.4.0
collected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;items

tests/api_test.py&lt;span class="w"&gt; &lt;/span&gt;..s&lt;span class="w"&gt;                                                              &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;75&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt;                                                            &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;==============================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;skipped&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.39s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==============================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can run the low-level tests on their own by passing a test matching expression to pytest, e.g. &lt;code&gt;pipenv run test -k service&lt;/code&gt;.
So how should we handle an invalid band?
Again this gives us a chance to do some design, think through how the function should behave by writing the test &lt;em&gt;before&lt;/em&gt; the implementation.
For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We could return &lt;code&gt;None&lt;/code&gt; for cases where the bands aren't valid, &lt;code&gt;assert resistance(["red"]) is None&lt;/code&gt;,  but if &lt;em&gt;all&lt;/em&gt; we get back from the function in the failing case is &lt;code&gt;None&lt;/code&gt; that doesn't tell us much about what the problem was;&lt;/li&gt;
&lt;li&gt;We could return a string describing the problem, but that would make it very difficult for the controller to distinguish between valid and invalid cases to send the appropriate responses;&lt;/li&gt;
&lt;li&gt;We could return some kind of object, &lt;code&gt;assert resistance(["red"]) == {"error": "..."}&lt;/code&gt;, but that doesn't exactly scream &lt;em&gt;"your input made no sense"&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I would say the right thing to do here is to &lt;strong&gt;throw an error&lt;/strong&gt;, which can have a message explaining what the problem was.
Remember that you have to use a &lt;em&gt;context manager&lt;/em&gt; when you expect an error to be thrown, to ensure the test can handle the error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_single_non_black_band_raises_error&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Call the shot&lt;/strong&gt;, run the test, check the diagnostics.&lt;/p&gt;
&lt;hr&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-k&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;service&lt;/span&gt;
&lt;span class="o"&gt;==================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===================================&lt;/span&gt;
platform&lt;span class="w"&gt; &lt;/span&gt;darwin&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.0,&lt;span class="w"&gt; &lt;/span&gt;pytest-8.3.2,&lt;span class="w"&gt; &lt;/span&gt;pluggy-1.5.0
rootdir:&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance
plugins:&lt;span class="w"&gt; &lt;/span&gt;anyio-4.4.0
collected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;items&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deselected&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;selected

tests/service_test.py&lt;span class="w"&gt; &lt;/span&gt;.F&lt;span class="w"&gt;                                                           &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;========================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FAILURES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;========================================&lt;/span&gt;
________________________&lt;span class="w"&gt; &lt;/span&gt;test_single_non_black_band_raises_error&lt;span class="w"&gt; &lt;/span&gt;_________________________

&lt;span class="w"&gt;    &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;test_single_non_black_band_raises_error&lt;span class="o"&gt;()&lt;/span&gt;:
&amp;gt;&lt;span class="w"&gt;       &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;pytest.raises&lt;span class="o"&gt;(&lt;/span&gt;ValueError&lt;span class="o"&gt;)&lt;/span&gt;:
E&lt;span class="w"&gt;       &lt;/span&gt;Failed:&lt;span class="w"&gt; &lt;/span&gt;DID&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;RAISE&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;class&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ValueError&amp;#39;&lt;/span&gt;&amp;gt;

tests/service_test.py:11:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Failed&lt;/span&gt;
&lt;span class="o"&gt;================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;short&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;summary&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=================================&lt;/span&gt;
FAILED&lt;span class="w"&gt; &lt;/span&gt;tests/service_test.py::test_single_non_black_band_raises_error&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Failed:&lt;span class="w"&gt; &lt;/span&gt;DID&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;RAISE&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;class&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ValueError&amp;#39;&lt;/span&gt;&amp;gt;
&lt;span class="o"&gt;=======================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deselected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.06s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;========================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Get that test passing at the service level, then run all of the tests to bring the integration tests back in (remember to &lt;strong&gt;call the shot&lt;/strong&gt;).&lt;/p&gt;
&lt;hr&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;==================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===================================&lt;/span&gt;
platform&lt;span class="w"&gt; &lt;/span&gt;darwin&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.0,&lt;span class="w"&gt; &lt;/span&gt;pytest-8.3.2,&lt;span class="w"&gt; &lt;/span&gt;pluggy-1.5.0
rootdir:&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance
plugins:&lt;span class="w"&gt; &lt;/span&gt;anyio-4.4.0
collected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;items

tests/api_test.py&lt;span class="w"&gt; &lt;/span&gt;..F&lt;span class="w"&gt;                                                              &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py&lt;span class="w"&gt; &lt;/span&gt;..&lt;span class="w"&gt;                                                           &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;========================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FAILURES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;========================================&lt;/span&gt;
___________________________&lt;span class="w"&gt; &lt;/span&gt;test_single_blue_band_responds_422&lt;span class="w"&gt; &lt;/span&gt;___________________________

&lt;span class="nv"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;httpx.Client&lt;span class="w"&gt; &lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;0x1034c3020&amp;gt;

&lt;span class="w"&gt;    &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;test_single_blue_band_responds_422&lt;span class="o"&gt;(&lt;/span&gt;client:&lt;span class="w"&gt; &lt;/span&gt;Client&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;client.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dict&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;bands&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]))&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt;       &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;response.status_code&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;HTTPStatus.UNPROCESSABLE_ENTITY
E&lt;span class="w"&gt;       &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;HTTPStatus.UNPROCESSABLE_ENTITY:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;&amp;gt;
E&lt;span class="w"&gt;        &lt;/span&gt;+&lt;span class="w"&gt;  &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;Response&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Internal&lt;span class="w"&gt; &lt;/span&gt;Server&lt;span class="w"&gt; &lt;/span&gt;Error&lt;span class="o"&gt;]&lt;/span&gt;&amp;gt;.status_code
E&lt;span class="w"&gt;        &lt;/span&gt;+&lt;span class="w"&gt;  &lt;/span&gt;and&lt;span class="w"&gt;   &lt;/span&gt;&amp;lt;HTTPStatus.UNPROCESSABLE_ENTITY:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;HTTPStatus.UNPROCESSABLE_ENTITY

tests/api_test.py:19:&lt;span class="w"&gt; &lt;/span&gt;AssertionError
----------------------------------&lt;span class="w"&gt; &lt;/span&gt;Captured&lt;span class="w"&gt; &lt;/span&gt;stdout&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;----------------------------------
INFO:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;127&lt;/span&gt;.0.0.1:64165&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GET /resistance?bands=blue HTTP/1.1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Internal&lt;span class="w"&gt; &lt;/span&gt;Server&lt;span class="w"&gt; &lt;/span&gt;Error
----------------------------------&lt;span class="w"&gt; &lt;/span&gt;Captured&lt;span class="w"&gt; &lt;/span&gt;stderr&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;----------------------------------
ERROR:&lt;span class="w"&gt;    &lt;/span&gt;Exception&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ASGI&lt;span class="w"&gt; &lt;/span&gt;application
Traceback&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;File&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path/to/resistance/app/__init__.py&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;_
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ResistanceModel&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;shorthand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;resistance&lt;span class="o"&gt;(&lt;/span&gt;bands&lt;span class="o"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                                     &lt;/span&gt;^^^^^^^^^^^^^^^^^
&lt;span class="w"&gt;  &lt;/span&gt;File&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path/to/resistance/app/service.py&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;resistance
&lt;span class="w"&gt;    &lt;/span&gt;raise&lt;span class="w"&gt; &lt;/span&gt;ValueError
ValueError
--------------------------------&lt;span class="w"&gt; &lt;/span&gt;Captured&lt;span class="w"&gt; &lt;/span&gt;stderr&lt;span class="w"&gt; &lt;/span&gt;teardown&lt;span class="w"&gt; &lt;/span&gt;--------------------------------
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Shutting&lt;span class="w"&gt; &lt;/span&gt;down
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Waiting&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;application&lt;span class="w"&gt; &lt;/span&gt;shutdown.
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Application&lt;span class="w"&gt; &lt;/span&gt;shutdown&lt;span class="w"&gt; &lt;/span&gt;complete.
INFO:&lt;span class="w"&gt;     &lt;/span&gt;Finished&lt;span class="w"&gt; &lt;/span&gt;server&lt;span class="w"&gt; &lt;/span&gt;process&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;19140&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;short&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;summary&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=================================&lt;/span&gt;
FAILED&lt;span class="w"&gt; &lt;/span&gt;tests/api_test.py::test_single_blue_band_responds_422&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;HTTPStatus.UNPROCESSABLE_ENTITY:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;&amp;gt;
&lt;span class="o"&gt;==============================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.28s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===============================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We only have one failing test and can see the error at the &lt;em&gt;business&lt;/em&gt; level, so we just need to catch it in the path operation function and respond appropriately to the request to get the tests passing.
Once you're there, make a commit.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;==================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===================================&lt;/span&gt;
platform&lt;span class="w"&gt; &lt;/span&gt;darwin&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.0,&lt;span class="w"&gt; &lt;/span&gt;pytest-8.3.2,&lt;span class="w"&gt; &lt;/span&gt;pluggy-1.5.0
rootdir:&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance
plugins:&lt;span class="w"&gt; &lt;/span&gt;anyio-4.4.0
collected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;items

tests/api_test.py&lt;span class="w"&gt; &lt;/span&gt;...&lt;span class="w"&gt;                                                              &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py&lt;span class="w"&gt; &lt;/span&gt;..&lt;span class="w"&gt;                                                           &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;===================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.24s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;====================================&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Handle single non-black band&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;3cebb2c&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Handle&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;non-black&lt;span class="w"&gt; &lt;/span&gt;band
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletion&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;app/service.py
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests/service_test.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Double trouble [5/9]&lt;/h2&gt;
&lt;p&gt;An obvious next step at this point is to test what happens with &lt;em&gt;two&lt;/em&gt; bands, which is also invalid according to our rules.
Let's add a low-level test case for two bands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_two_bands_raises_error&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It's worth noting that I've chosen to have &lt;code&gt;"black"&lt;/code&gt; as the first of two bands &lt;em&gt;specifically&lt;/em&gt;; this &lt;em&gt;was&lt;/em&gt; a valid first band for a 0Ω resistor, but isn't otherwise.
Any two-band "resistor" is invalid, but using this test case rules out the possibility that we &lt;em&gt;only&lt;/em&gt; check whether the first band is black (and not e.g. how many there are).&lt;/p&gt;
&lt;p&gt;Call the shot, run the test.
If it fails (it may not, depending on how you've implemented the service so far!) then get it passing.
We already know that the API will respond 422 if the service throws an error, so we're done; make a commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;==================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===================================&lt;/span&gt;
platform&lt;span class="w"&gt; &lt;/span&gt;darwin&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.0,&lt;span class="w"&gt; &lt;/span&gt;pytest-8.3.2,&lt;span class="w"&gt; &lt;/span&gt;pluggy-1.5.0
rootdir:&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance
plugins:&lt;span class="w"&gt; &lt;/span&gt;anyio-4.4.0
collected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;items

tests/api_test.py&lt;span class="w"&gt; &lt;/span&gt;...&lt;span class="w"&gt;                                                              &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py&lt;span class="w"&gt; &lt;/span&gt;...&lt;span class="w"&gt;                                                          &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;===================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.36s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;====================================&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Error for two bands&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;6dae036&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Error&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;two&lt;span class="w"&gt; &lt;/span&gt;bands
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Plotting a course [6/9]&lt;/h2&gt;
&lt;p&gt;Now we're in a nice position - we've designed and implemented an API, factored our app into &lt;em&gt;transport&lt;/em&gt; and &lt;em&gt;business&lt;/em&gt; domains, and are testing the integration across three cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No bands - &lt;em&gt;structurally&lt;/em&gt; invalid, service doesn't get called, 400 response;&lt;/li&gt;
&lt;li&gt;One black band - service gets called, 200 response with its return value; and&lt;/li&gt;
&lt;li&gt;One non-black band or two bands - &lt;em&gt;semantically&lt;/em&gt; invalid, service gets called, 422 response on error.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sure, we're only dealing with a single, trivial valid case: a 0Ω resistor, with a single black band (which is basically just a wire in the packaging of a resistor!)
Our code isn't going to help our end users much at this stage, but we've set the foundations to be able to confidently and rapidly iterate on the core functionality.
And if the user &lt;em&gt;does&lt;/em&gt; have a resistor with a single black band it gives them the correct answer!&lt;/p&gt;
&lt;p&gt;Now, how to approach the more useful cases and actually return some non-zero answers?&lt;/p&gt;
&lt;p&gt;In general, when I'm trying to work my way through a problem like this, I try to think about what the next &lt;em&gt;simplest&lt;/em&gt; step is - not just in the implementation to get the test passing, but in the &lt;em&gt;logic&lt;/em&gt; to write a failing test.
Let's keep using the 22,000Ω/&lt;code&gt;"22K"&lt;/code&gt; case we started with.
Thinking about the three bands we are using, I'd propose that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;second&lt;/strong&gt; band is the simplest to deal with, as it can represent any value 0-9 (&lt;code&gt;"20K"&lt;/code&gt;, &lt;code&gt;"21K"&lt;/code&gt;, ...); then&lt;/li&gt;
&lt;li&gt;The first band is the next simplest, as it can represent 1-9 (&lt;code&gt;"12K"&lt;/code&gt;, &lt;code&gt;"22K"&lt;/code&gt;, ...) but not 0 (throws an error leading to 422 response status); and finally&lt;/li&gt;
&lt;li&gt;The third is the most complex, as both the character and its &lt;em&gt;position&lt;/em&gt; can change (&lt;code&gt;"22R"&lt;/code&gt;, &lt;code&gt;"220R"&lt;/code&gt;, ...).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To keep us on track as we work towards the result, start with an integration-level test for a &lt;em&gt;different&lt;/em&gt; example, one we're not actually going to reach until all three bands are handled.
E.g. if the unit-level cases are based around the 22,000Ω example, use the 6,800,000Ω example for the integration-level case.
That stops us getting overexcited and shipping once we've handled both value bands but not yet the multiplier.
The alternative would be to ensure that cases that aren't yet supported explicitly throw an error, returning a 422 status, which means adding extra tests early on then deleting them as they become irrelevant (this is also an acceptable part of TDD).&lt;/p&gt;
&lt;p&gt;So work through the cases in that order, writing &lt;a href="https://docs.pytest.org/en/stable/example/parametrize.html"&gt;&lt;em&gt;parameterised tests&lt;/em&gt;&lt;/a&gt; for each group.
By the time you're finished the suite at the service level should look something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pytest&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;resistance&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_single_black_band_returns_0R&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_single_non_black_band_raises_error&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_two_bands_raises_error&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;


&lt;span class="nd"&gt;@pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;second_band,shorthand&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;20K&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_three_bands_second_band_returns_correct_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;second_band&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shorthand&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;red&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;second_band&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;orange&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;shorthand&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_three_bands_black_first_band_raises_error&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;


&lt;span class="nd"&gt;@pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;first_band,shorthand&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_three_bands_first_band_returns_correct_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_band&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shorthand&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;


&lt;span class="nd"&gt;@pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;third_band,shorthand&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_three_bands_third_band_returns_correct_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;third_band&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shorthand&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Giving test outputs like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--verbose
&lt;span class="o"&gt;==================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===================================&lt;/span&gt;
platform&lt;span class="w"&gt; &lt;/span&gt;darwin&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.0,&lt;span class="w"&gt; &lt;/span&gt;pytest-8.3.2,&lt;span class="w"&gt; &lt;/span&gt;pluggy-1.5.0&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;path/to/virtualenvs/resistance-UW3A4gHD/bin/python
cachedir:&lt;span class="w"&gt; &lt;/span&gt;.pytest_cache
rootdir:&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance
plugins:&lt;span class="w"&gt; &lt;/span&gt;anyio-4.4.0
collected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;33&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;items

tests/api_test.py::test_single_black_band_returns_0R&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/api_test.py::test_no_bands_responds_400&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt;                               &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/api_test.py::test_single_blue_band_responds_422&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/api_test.py::test_blue_grey_red_returns_6M8&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt;                           &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_single_black_band_returns_0R&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_single_non_black_band_raises_error&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_two_bands_raises_error&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_second_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;black-20K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_second_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;brown-21K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_second_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;red-22K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_second_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;orange-23K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;33&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_second_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;yellow-24K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;36&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_second_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;green-25K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;39&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_second_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;blue-26K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;42&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_second_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;violet-27K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;45&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_second_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;grey-28K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;48&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_second_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;white-29K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;51&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_black_first_band_raises_error&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;54&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_first_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;brown-12K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;57&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_first_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;orange-32K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_first_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;yellow-42K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;63&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_first_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;green-52K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;66&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_first_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;blue-62K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_first_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;violet-72K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;72&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_first_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;grey-82K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;75&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_first_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;white-92K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;78&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_third_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;black-22R&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;81&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_third_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;brown-220R&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;84&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_third_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;red-2K2&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;87&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_third_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;yellow-220K&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;90&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_third_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;green-2M2&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;93&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_third_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;blue-22M&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;96&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;
tests/service_test.py::test_three_bands_third_band_returns_correct_result&lt;span class="o"&gt;[&lt;/span&gt;violet-220M&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASSED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;===================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;33&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.25s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once everything's passing, make a commit.&lt;/p&gt;
&lt;h2&gt;Four bands [7/9]&lt;/h2&gt;
&lt;p&gt;We can handle all valid one- and three-band resistors at this point, plus some invalid one- and two-band cases.
So let's handle resistors with &lt;em&gt;three&lt;/em&gt; value bands, adding an extra significant figure to the value.&lt;/p&gt;
&lt;p&gt;Again it's important to think about the cases we're going to choose to ensure our code works correctly.
I would suggest at least three, based on the &lt;em&gt;structure&lt;/em&gt; of the output:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Where the multiplier is a multiple of 3 (0, 3, 6, 9), i.e. the band is black, orange, blue or white, we already showed three digits, e.g. &lt;code&gt;"120R"&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Otherwise, we only showed two digits before, e.g. &lt;code&gt;"12K"&lt;/code&gt; or &lt;code&gt;"1M2"&lt;/code&gt;, so we're adding a third digit;&lt;/li&gt;
&lt;li&gt;Unless the third value band is black, in which case we still shouldn't show a trailing zero.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here the cases we've selected have a meaning, so the name should clarify that meaning to the reader rather than just e.g. &lt;code&gt;"returns 123K for brown, red, orange, orange"&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_four_bands_adds_third_digit_in_the_middle&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;brown&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;red&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;orange&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;orange&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;123K&amp;quot;&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_four_bands_adds_third_digit_at_the_end&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;brown&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;yellow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;violet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;brown&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1K47&amp;quot;&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_four_bands_does_not_add_trailing_zero&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;grey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;green&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;68M&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Introduce these tests (along with an API integration case, if you like), get everything passing and make a commit.&lt;/p&gt;
&lt;h2&gt;Paradox of tolerance [8/9]&lt;/h2&gt;
&lt;p&gt;Now we're going to add a fourth rule of resistors:&lt;/p&gt;
&lt;ol start="4"&gt;
  &lt;li&gt;A resistor may have a &lt;em&gt;tolerance&lt;/em&gt; band (otherwise its tolerance is ±20%), unless it's a 0Ω resistor.
&lt;/ol&gt;

&lt;p&gt;We'll cover five possible cases here, which include two new band colours and reuse two of the existing colours:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;±20%&lt;/th&gt;
&lt;th&gt;±10%&lt;/th&gt;
&lt;th&gt;±5%&lt;/th&gt;
&lt;th&gt;±2%&lt;/th&gt;
&lt;th&gt;±1%&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;No band&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: gold; background-color: black"&gt;&amp;nbsp;gold&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: silver; background-color: black"&gt;&amp;nbsp;silver&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: brown"&gt;brown&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is, as you may just have realised, a bit of a problem.
If the tolerance band is optional, then what is e.g. &lt;strong&gt;&lt;span style="color: brown"&gt;brown&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: green"&gt;green&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: yellow; background-color: black"&gt;&amp;nbsp;yellow&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt; describing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;15,400Ω ±20%; or&lt;/li&gt;
&lt;li&gt;150,000Ω ±2%?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Obviously that's quite a big difference; the circuit probably isn't going to work correctly if you use the wrong one!
On the physical packaging this is indicated by a gap - the value and multiplier bands are at one end of the resisistor, the tolerance band is at the other.
Perhaps we could do something similar, adding a separate parameter at the service level and a separate query parameter to the HTTP API?
For example, maybe something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://localhost:3000/resistance?bands=brown&amp;amp;bands=green&amp;amp;bands=yellow&amp;amp;bands=red&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shorthand&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;15K4&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;tolerance&amp;quot;&lt;/span&gt;:0.2&lt;span class="o"&gt;}&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://localhost:3000/resistance?bands=brown&amp;amp;bands=green&amp;amp;bands=yellow&amp;amp;tolerance=red&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shorthand&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;150K&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;tolerance&amp;quot;&lt;/span&gt;:0.02&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here we're changing the responses for existing requests - now rather than &lt;code&gt;{"shorthand":"15K4"}&lt;/code&gt;, we get &lt;code&gt;{"shorthand":"15K4","tolerance":0.2}&lt;/code&gt;.
I'd suggest making this change first, as a separate commit, then moving on to include the actual tolerance bands.&lt;/p&gt;
&lt;p&gt;Design the API and test-drive the implementation of your choice, starting with an integration test then driving out the full functionality through some unit tests.&lt;/p&gt;
&lt;p&gt;Once you're happy, make a final commit - we're done!&lt;/p&gt;
&lt;h2&gt;Exercises [9/9]&lt;/h2&gt;
&lt;p&gt;Here are some follow-up tasks for further practice (remember to &lt;strong&gt;test-drive&lt;/strong&gt; anything you work on):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Predict and then check happens if you make a request where the bands aren't recognised colours (e.g. &lt;code&gt;GET /resistance?bands=fuchsia&amp;amp;bands=goldenrod&amp;amp;bands=octarine&lt;/code&gt;) and/or there are multiple tolerance bands. Did you predict correctly? Do you think it's the &lt;em&gt;right&lt;/em&gt; behaviour - do you consider that request to be &lt;em&gt;semantically&lt;/em&gt; or &lt;em&gt;structurally&lt;/em&gt; invalid, and does the current implementation reflect that? If you think it should behave differently, update accordingly.&lt;/li&gt;
&lt;li&gt;Return to step 4 and try out some different orders for introducing the three-band cases - did I suggest the right route, how much difference does it make?&lt;/li&gt;
&lt;li&gt;Design and develop a different HTTP API (i.e. changing any or all of the request method, request path, use of query parameters or structure of the response body).&lt;/li&gt;
&lt;li&gt;As well as the &lt;em&gt;value&lt;/em&gt;, &lt;em&gt;multiplier&lt;/em&gt; and &lt;em&gt;tolerance&lt;/em&gt; bands, resistors may have a &lt;em&gt;temperature coefficient&lt;/em&gt; band - implement support for this.&lt;/li&gt;
&lt;li&gt;There's a set of &lt;a href="https://en.wikipedia.org/wiki/E_series_of_preferred_numbers"&gt;preferred numbers&lt;/a&gt; that resistors are generally designed to (e.g. for the default ±20% tolerance you'd get resistors only in multiples of 1.0, 1.5, 2.2, 3.3, 4.7 or 6.8) - introduce a "strict" mode in which non-preferred resistors are invalid inputs.&lt;/li&gt;
&lt;li&gt;Write a CLI to expose the core functionality on the command line (you can use Python's built-in &lt;a href="https://docs.python.org/3/library/argparse.html"&gt;&lt;code&gt;argparse&lt;/code&gt;&lt;/a&gt; to help you out) e.g. &lt;code&gt;pipenv run cli red green blue --tolerance silver&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I'd recommend creating a new git branch for each one you try (e.g. use &lt;code&gt;git checkout -b &amp;lt;name&amp;gt;&lt;/code&gt;) and making commits as appropriate.&lt;/p&gt;
&lt;h2 id="bonus"&gt;The fix(ture) is in [Bonus]&lt;/h2&gt;
&lt;p&gt;The content of &lt;code&gt;conftest.py&lt;/code&gt; may look a little complicated, and uses some moderately advanced Python language features, but it allows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;our tests to make requests to a real server instance, avoiding the issue of some errors not being turned into responses by the &lt;code&gt;TestClient&lt;/code&gt; setup; and&lt;/li&gt;
&lt;li&gt;the setup and teardown to be handled outside of each individual test.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; the core logic for running the server on a separate thread was taken from &lt;a href="https://bugfactory.io/articles/starting-and-stopping-uvicorn-in-the-background/"&gt;&lt;em&gt;"Starting and Stopping &lt;code&gt;uvicorn&lt;/code&gt; in the Background"&lt;/em&gt;&lt;/a&gt; by Christoph Schiessl.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;collections.abc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Generator&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;socket&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;threading&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Thread&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;httpx&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;uvicorn&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;


&lt;span class="nd"&gt;@pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;module&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Generator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;TestServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random_port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;random_port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TestServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;socket_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;socket_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket_&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sockets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_socket&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__enter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TestServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__exit__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_tb&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;should_exit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getsockname&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In a bit more detail, focusing on the fixture function itself, here's what's happening:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The first line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nd"&gt;@pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;module&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;is a &lt;em&gt;decorator&lt;/em&gt; registering the function as a pytest fixture, i.e. that its going to provide a value to be used in individual test cases.
The module scope means that the fixture will only be called once for any given module that it's used in, and all of the tests in the same module will receive the same value (so in our case, all of the tests in &lt;code&gt;tests/api_test.py&lt;/code&gt; will make requests to the same server thread).
 Without this, the default scope &lt;code&gt;"function"&lt;/code&gt; would be used, and a separate server created for each test (if you try this you can see it takes much longer to run the suite).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the next line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Generator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;the fixture name is declared as &lt;code&gt;client&lt;/code&gt; and the return type is declared as a &lt;a href="https://wiki.python.org/moin/Generators"&gt;generator&lt;/a&gt;, i.e. this is a function that's going to &lt;em&gt;yield&lt;/em&gt; one or more values, stopping its own execution until control is returned to it.
In pytest, generators (or &lt;a href="https://docs.pytest.org/en/stable/how-to/fixtures.html#yield-fixtures-recommended"&gt;"yield fixtures"&lt;/a&gt;) are used to allow a fixture to be re-entered &lt;em&gt;after&lt;/em&gt; the test cases have run to do any necessary cleanup and teardown.&lt;/p&gt;
&lt;p&gt;In this case we only care about the type of value that's yielded, not what can be sent back to the generator or is eventually returned, so the other generics are just filled with &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Within the fixture, we first create a server that wraps our FastAPI application, listening on a random port:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;TestServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random_port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;TestServer&lt;/code&gt; is a custom class written for this purpose; we'll go into a bit more detail on how this works below.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The next step before the fixture value is ready to be injected into the tests is creating an &lt;a href="https://www.python-httpx.org/advanced/clients/"&gt;&lt;code&gt;httpx&lt;/code&gt; client&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This uses a &lt;em&gt;context manager&lt;/em&gt;, per the documentation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The recommended way to use a &lt;code&gt;Client&lt;/code&gt; is as a context manager.
This will ensure that connections are properly cleaned up when leaving the &lt;code&gt;with&lt;/code&gt; block&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Setting the base URL allows the tests to make &lt;em&gt;relative&lt;/em&gt; requests like &lt;code&gt;client.get("/resistance")&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now the client object is yielded, providing the value of the fixture for the tests:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The fixture function pauses execution at this point, waiting while pytest runs the tests that require the value.
Once all of the tests in the module have finished running, pytest &lt;em&gt;re-enters&lt;/em&gt; the fixture function, allowing execution to continue from the next line.
This allows the cleanup on exiting the two context managers to be deferred - if this was &lt;code&gt;return client&lt;/code&gt; instead, the client would be closed and the server shut down &lt;em&gt;before&lt;/em&gt; sending the value to the tests, so they'd all fail with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;RuntimeError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cannot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;been&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;closed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So how does the &lt;code&gt;TestServer&lt;/code&gt; work?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A &lt;em&gt;class method&lt;/em&gt; is used to create a new instance, creating a new socket listening on a random port on the local host then instantiating the class with that socket and the app:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nd"&gt;@classmethod&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;random_port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TestServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;socket_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;socket_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When a new instance is created, it creates a Uvicorn server wrapping the application (this is the same server FastAPI uses for its &lt;code&gt;dev&lt;/code&gt; and &lt;code&gt;run&lt;/code&gt; CLI commands) and the &lt;a href="https://docs.python.org/3/library/threading.html"&gt;thread&lt;/a&gt; that allows the server to run in the background while our tests are executed.
    Note the &lt;code&gt;_&lt;/code&gt; prefix - this indicates to users of the class that these attributes should be considered &lt;em&gt;private&lt;/em&gt; and not accessed directly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket_&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sockets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_socket&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;target&lt;/code&gt; argument to the thread is the callable that should be executed in the new thread.
The &lt;code&gt;kwargs&lt;/code&gt; argument defines additional keyword arguments that should be passed to the target callable when it's invoked.
So when the thread starts, it will effectively run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sockets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_socket&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When the context manager &lt;code&gt;with&lt;/code&gt; block is &lt;em&gt;entered&lt;/em&gt;, the &lt;code&gt;__enter__&lt;/code&gt; method is called, which starts the thread created in &lt;code&gt;__init__&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__enter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TestServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Returning &lt;code&gt;self&lt;/code&gt; allows the &lt;code&gt;TestServer&lt;/code&gt; instance to be accessed &lt;code&gt;as server&lt;/code&gt; inside the &lt;code&gt;with&lt;/code&gt; block.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Inside the &lt;code&gt;with&lt;/code&gt; block, the client is created. This uses the &lt;code&gt;url&lt;/code&gt; property from the test server, which is determined based on the host and port from the socket the underlying Uvicorn server is using:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nd"&gt;@property&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getsockname&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once the tests have all run and the client has been closed (by exiting its own context manager), the server &lt;code&gt;with&lt;/code&gt; block is exited, so the &lt;code&gt;__exit__&lt;/code&gt; method is called:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__exit__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_tb&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;should_exit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This tells the Uvicorn server to stop accepting any new requests and prepare for shutdown.
Joining the thread means the fixture function will now wait for that thread (and hence the server process) to exit before allowing the test suite to complete.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fixtures are a powerful way to abstract setup and teardown out of your tests to keep them readable; here's an example of using them to test actual spacecraft 🚀:&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/spCOYV4KyPA?si=accWVqeMBQn85oM7" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;

&lt;hr&gt;
&lt;p&gt;&lt;sup&gt;1&lt;/sup&gt; Actually, the proper &lt;a href="https://en.wikipedia.org/wiki/RKM_code"&gt;RKM code&lt;/a&gt; uses trailing zeros to indicate a tighter tolerance, we will ignore that distinction for now.&lt;/p&gt;</content><category term="development"></category><category term="python"></category><category term="tdd"></category><category term="xp"></category></entry><entry><title>Python TDD FTW</title><link href="https://blog.jonrshar.pe/2024/Aug/17/python-tdd-ftw.html" rel="alternate"></link><published>2024-08-17T11:30:00+01:00</published><updated>2024-08-17T11:30:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2024-08-17:/2024/Aug/17/python-tdd-ftw.html</id><summary type="html">&lt;p&gt;Test-driven Python development done right - part 1&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: this was originally published as &lt;a href="https://blog.jonrshar.pe/2020/Aug/31/js-tdd-ftw.html"&gt;JS TDD FTW&lt;/a&gt;, using JavaScript with Jest, but I've recently been working with a client using &lt;a href="https://www.python.org/"&gt;Python&lt;/a&gt;, so this article is an updated version of the same example using the latter tech stack.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the key Extreme Programming (&lt;a href="http://wiki.c2.com/?ExtremeProgramming"&gt;XP&lt;/a&gt;) engineering practices is test-driven development (TDD), usually expressed as repeatedly following this simple, three-step process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Red&lt;/strong&gt; - write a failing test that describes the behaviour you want;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Green&lt;/strong&gt; - write the simplest possible code to make the test pass; and&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refactor&lt;/strong&gt; - clean up your code without breaking the tests.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Below I'm going to give a proper example of vanilla Python TDD done &lt;em&gt;"the right way"&lt;/em&gt;, sprinkling some bonus command line and git practice throughout.&lt;/p&gt;
&lt;h3&gt;Requirements&lt;/h3&gt;
&lt;p&gt;I've aimed this content at more junior developers, so there are more explanations than all readers will need, but anyone new to testing and TDD should find something to take from it. We'll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;*nix command line: already provided on macOS and Linux; if you're using Windows try &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/about"&gt;WSL&lt;/a&gt; or &lt;a href="https://gitforwindows.org/"&gt;Git BASH&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;Python installed (any 3.x version should work); and&lt;/li&gt;
&lt;li&gt;Familiarity with basic Python syntax.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We also need something to implement. &lt;a href="https://en.wikipedia.org/wiki/Rock_paper_scissors"&gt;Rock Paper Scissors&lt;/a&gt; (or just RPS) is a simple playground game that takes some inputs (the shapes that the players present) and gives a single output (the outcome), which makes it a good fit for a simple function to test drive.
If you're not familiar with the rules, read the linked Wikipedia article before continuing.&lt;/p&gt;
&lt;p&gt;Before we get into the TDD process, think about what the code for an implementation of RPS might look like.
Don't write any code yet (we don't have the failing tests to make us do that!) but imagine a function - what parameters would it accept?
What would it return?
We're expecting different outputs for different inputs, which implies some conditional logic - what conditions do you think would be involved?
Note your ideas down, we'll revisit them later.&lt;/p&gt;
&lt;p&gt;As we go through, please carefully &lt;em&gt;read everything&lt;/em&gt;. I'd recommend &lt;em&gt;typing the code&lt;/em&gt; rather than copy-pasting, especially if you're a new developer; it's good practice to build your muscle memory.&lt;/p&gt;
&lt;h2&gt;Setup [1/10]&lt;/h2&gt;
&lt;p&gt;Let's get up and running. Starting in your working directory (e.g. I use &lt;code&gt;~/workspace&lt;/code&gt;), run the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;rps-tdd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--allow-empty&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Initial commit&amp;#39;&lt;/span&gt;
Initialized&lt;span class="w"&gt; &lt;/span&gt;empty&lt;span class="w"&gt; &lt;/span&gt;Git&lt;span class="w"&gt; &lt;/span&gt;repository&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd/.git/
&lt;span class="o"&gt;[&lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;root-commit&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Initial&lt;span class="w"&gt; &lt;/span&gt;commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;By chaining multiple commands using &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; (&lt;em&gt;"and"&lt;/em&gt;, assuming the previous commands all succeeded), this will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a new directory named &lt;code&gt;rps-tdd/&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Switch into it (&lt;code&gt;$_&lt;/code&gt; references the argument to the last command, see e.g. &lt;a href="https://stackoverflow.com/q/30154694/3001761"&gt;this SO question&lt;/a&gt;);&lt;/li&gt;
&lt;li&gt;Initialise a new git repository; and&lt;/li&gt;
&lt;li&gt;Create an empty initial commit (&lt;code&gt;--allow-empty&lt;/code&gt; lets us create this first commit without having any content, and &lt;code&gt;-m&lt;/code&gt; lets us supply the commit message on the command line).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Running tests [2/10]&lt;/h2&gt;
&lt;p&gt;Next, we're going to need something to &lt;em&gt;run&lt;/em&gt; our tests.
To keep things simple, we're going to start with one that's built right into the standard library: &lt;a href="https://docs.python.org/3/library/unittest.html"&gt;&lt;code&gt;unittest&lt;/code&gt;&lt;/a&gt;.
This is an xUnit-style framework, where test suites and cases are represented as classes and methods respectively.
Run it as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest

----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000s

NO&lt;span class="w"&gt; &lt;/span&gt;TESTS&lt;span class="w"&gt; &lt;/span&gt;RAN
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is a little bit unhappy, it has a non-zero exit code, but you can see why - there were no tests.
Let's give it a simple test to run; create a file named &lt;code&gt;tests.py&lt;/code&gt; containing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_work&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now run the tests a second time:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest
.
----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000s

OK
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Much happier!
So what does that test &lt;em&gt;do&lt;/em&gt;, what's going on there?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We create a class named &lt;code&gt;Tests&lt;/code&gt; that extends &lt;code&gt;unittest&lt;/code&gt;'s built-in &lt;code&gt;TestCase&lt;/code&gt;. This represents our test suite.&lt;/li&gt;
&lt;li&gt;We create a method to &lt;strong&gt;register a test&lt;/strong&gt;. This represents an individual test within the suite - when we run the tests, &lt;code&gt;unittest&lt;/code&gt; will call each test method to run the test. The method name is the name of our test, and must start with &lt;code&gt;test_&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Within the test body, we &lt;strong&gt;establish our expectations&lt;/strong&gt;. What exactly do we think should happen? I've split this into three sections:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Arrange&lt;/strong&gt; (sometimes known as &lt;em&gt;"given"&lt;/em&gt;) - set up the preconditions for our test, in this case two initial values.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Act&lt;/strong&gt; (or &lt;em&gt;"when"&lt;/em&gt;) - do some work, in this case adding them together. This is what we're actually testing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assert&lt;/strong&gt; (or &lt;em&gt;"then"&lt;/em&gt;) - make sure that the work was done correctly. &lt;code&gt;TestCase&lt;/code&gt; has a lot of helpful &lt;a href="https://docs.python.org/3/library/unittest.html#assert-methods"&gt;matcher methods&lt;/a&gt; to describe our expectations, taking the &lt;em&gt;actual&lt;/em&gt; and &lt;em&gt;expected&lt;/em&gt; values and comparing them.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The point of tests is to give us good feedback.
If we had an inaccurate expectation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;        result = left + right

&lt;span class="gd"&gt;-        self.assertEqual(result, 3)&lt;/span&gt;
&lt;span class="gi"&gt;+        self.assertEqual(result, 5)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;it would tell us exactly what the problem was:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest
&lt;span class="nv"&gt;F&lt;/span&gt;
&lt;span class="o"&gt;======================================================================&lt;/span&gt;
FAIL:&lt;span class="w"&gt; &lt;/span&gt;test_should_work&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;tests.Tests.test_should_work&lt;span class="o"&gt;)&lt;/span&gt;
----------------------------------------------------------------------
Traceback&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;File&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path/to/rps-tdd/tests.py&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;test_should_work
&lt;span class="w"&gt;    &lt;/span&gt;self.assertEqual&lt;span class="o"&gt;(&lt;/span&gt;result,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
AssertionError:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;

----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000s

FAILED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;failures&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Protip&lt;/strong&gt;: always read the outputs carefully! Sometimes a test fails for an unexpected reason, which usually tells you something interesting.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, we're happy things are working so far.&lt;/p&gt;
&lt;h2&gt;A failing test [3/10]&lt;/h2&gt;
&lt;p&gt;Let's start some actual TDD, and write our first failing test. Replace the content of &lt;code&gt;tests.py&lt;/code&gt; with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RpsTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_rock_vs_scissors_left_wins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Our first test is that, given that &lt;code&gt;left&lt;/code&gt; is &lt;code&gt;"rock"&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt; is &lt;code&gt;"scissors"&lt;/code&gt; (&lt;em&gt;"Arrange"&lt;/em&gt;), when the shapes are compared (&lt;em&gt;"Act"&lt;/em&gt;) , then the winner should be &lt;code&gt;"left"&lt;/code&gt; (&lt;em&gt;"Assert"&lt;/em&gt;) because rock blunts scissors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; one key benefit of TDD here - we can try out how we should interact with our code (its &lt;em&gt;"interface"&lt;/em&gt;) before we've even written any.
Maybe it should return something other than a string, for example?
We can have that discussion now, while it's just a matter of changing our minds rather than the code.&lt;/p&gt;
&lt;p&gt;Before we run the first test, &lt;a href="https://markhneedham.com/blog/2010/07/28/tdd-call-your-shots/"&gt;&lt;em&gt;"call the shot"&lt;/em&gt;&lt;/a&gt; - make a prediction of what the test result will be, pass or fail.
If you think the test will fail, &lt;strong&gt;why&lt;/strong&gt;; will the expectation be unmet (and what value do you think you'll get instead) or will something else go wrong?
This is really good practice for &lt;em&gt;"playing computer"&lt;/em&gt; (modelling the behaviour of the code in your head) and you can write your guess down (or say it out loud if you're pairing) to keep yourself honest.
Now let's run it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest
&lt;span class="nv"&gt;E&lt;/span&gt;
&lt;span class="o"&gt;======================================================================&lt;/span&gt;
ERROR:&lt;span class="w"&gt; &lt;/span&gt;test_rock_vs_scissors_left_wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;tests.RpsTests.test_rock_vs_scissors_left_wins&lt;span class="o"&gt;)&lt;/span&gt;
----------------------------------------------------------------------
Traceback&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;File&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path/to/rps-tdd/tests.py&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;test_rock_vs_scissors_left_wins
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rps&lt;span class="o"&gt;(&lt;/span&gt;left,&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;^^^
NameError:&lt;span class="w"&gt; &lt;/span&gt;name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rps&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;defined

----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.001s

FAILED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;...were you right?&lt;/p&gt;
&lt;h2&gt;The simplest possible change [4/10]&lt;/h2&gt;
&lt;p&gt;As you may have guessed, this fails because &lt;code&gt;rps&lt;/code&gt; &lt;em&gt;doesn't exist yet&lt;/em&gt;.
Let's make the simplest possible change that will at least change the error we're receiving; define the function.
At this stage we could &lt;code&gt;import&lt;/code&gt; the function from another file, but let's keep things simple for now; add the following to the top of &lt;code&gt;tests.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest
&lt;span class="nv"&gt;E&lt;/span&gt;
&lt;span class="o"&gt;======================================================================&lt;/span&gt;
ERROR:&lt;span class="w"&gt; &lt;/span&gt;test_rock_vs_scissors_left_wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;tests.RpsTests.test_rock_vs_scissors_left_wins&lt;span class="o"&gt;)&lt;/span&gt;
----------------------------------------------------------------------
Traceback&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;File&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path/to/rps-tdd/tests.py&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;test_rock_vs_scissors_left_wins
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rps&lt;span class="o"&gt;(&lt;/span&gt;left,&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;^^^^^^^^^^^^^^^^
TypeError:&lt;span class="w"&gt; &lt;/span&gt;rps&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;takes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;positional&lt;span class="w"&gt; &lt;/span&gt;arguments&lt;span class="w"&gt; &lt;/span&gt;but&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;were&lt;span class="w"&gt; &lt;/span&gt;given

----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000s

FAILED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Our test still doesn't pass, but at least we've changed the error message.
So let's make the simplest possible change that should at least change the error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gd"&gt;-def rps():&lt;/span&gt;
&lt;span class="gi"&gt;+def rps(left, right):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    pass
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest
&lt;span class="nv"&gt;F&lt;/span&gt;
&lt;span class="o"&gt;======================================================================&lt;/span&gt;
FAIL:&lt;span class="w"&gt; &lt;/span&gt;test_rock_vs_scissors_left_wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;tests.RpsTests.test_rock_vs_scissors_left_wins&lt;span class="o"&gt;)&lt;/span&gt;
----------------------------------------------------------------------
Traceback&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;File&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path/to/rps-tdd/tests.py&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;test_rock_vs_scissors_left_wins
&lt;span class="w"&gt;    &lt;/span&gt;self.assertEqual&lt;span class="o"&gt;(&lt;/span&gt;result,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
AssertionError:&lt;span class="w"&gt; &lt;/span&gt;None&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;

----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000s

FAILED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;failures&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The test now fails (&lt;code&gt;F&lt;/code&gt;) instead of throwing an error (&lt;code&gt;E&lt;/code&gt;), we're now reaching the actual expectation instead of crashing when we try to call the function.
So let's make the simplest possible change that should get this passing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;def rps(left, right):
&lt;span class="gd"&gt;-    pass&lt;/span&gt;
&lt;span class="gi"&gt;+    return &amp;quot;left&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest
.
----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000s

OK
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Great!
This calls for a celebratory commit.
But have a look at what's currently in your repo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Untracked&lt;span class="w"&gt; &lt;/span&gt;files:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git add &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;include&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;what&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;__pycache__/
&lt;span class="w"&gt;    &lt;/span&gt;tests.py

nothing&lt;span class="w"&gt; &lt;/span&gt;added&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;but&lt;span class="w"&gt; &lt;/span&gt;untracked&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;present&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git add&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;track&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;tests.py&lt;/code&gt; is the file we've been working on, but we don't want to track changes to the &lt;code&gt;__pycache__/&lt;/code&gt; directory (for more on what it is, see &lt;a href="https://stackoverflow.com/q/16869024/3001761"&gt;this SO question&lt;/a&gt;).
Create a &lt;code&gt;.gitignore&lt;/code&gt; containing the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;__pycache__/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The status should now update accordingly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Untracked&lt;span class="w"&gt; &lt;/span&gt;files:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git add &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;include&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;what&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;.gitignore
&lt;span class="w"&gt;    &lt;/span&gt;tests.py

nothing&lt;span class="w"&gt; &lt;/span&gt;added&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;but&lt;span class="w"&gt; &lt;/span&gt;untracked&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;present&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git add&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;track&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So we can make a commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;First test - rock vs. scissors&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;544d1ac&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;First&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;.gitignore
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; another key benefit of TDD here - it tells you when you're done. Once the tests are passing, the implementation meets the current requirements.&lt;/p&gt;
&lt;h2&gt;The difficult second test [5/10]&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;"But wait"&lt;/em&gt;, you might be thinking, &lt;em&gt;"that's pointless, it doesn't &lt;strong&gt;do&lt;/strong&gt; anything!"&lt;/em&gt;
And to an extent, that's true; our function just returns a hard-coded string.
But let's think about what else has happened:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We've decided on an interface for our function, what it's going to receive and return;&lt;/li&gt;
&lt;li&gt;We've proved out a test setup that lets us make assertions on the behaviour of that function; and&lt;/li&gt;
&lt;li&gt;We've created the simplest possible implementation for the requirements we've expressed through tests so far, making our code very robust.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So let's build on that foundation; flip the shapes around to change the output so we can expect the test to fail.
Add the following into the &lt;code&gt;RpsTests&lt;/code&gt; suite in &lt;code&gt;tests.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_scissors_vs_rock_right_wins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that the &lt;em&gt;"Act"&lt;/em&gt; is the same, but the &lt;em&gt;"Arrange"&lt;/em&gt; and &lt;em&gt;"Assert"&lt;/em&gt; have changed.
Call the shot, then run the test again to see if you were correct:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest
.F
&lt;span class="o"&gt;======================================================================&lt;/span&gt;
FAIL:&lt;span class="w"&gt; &lt;/span&gt;test_scissors_vs_rock_right_wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;tests.RpsTests.test_scissors_vs_rock_right_wins&lt;span class="o"&gt;)&lt;/span&gt;
----------------------------------------------------------------------
Traceback&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;File&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path/to/rps-tdd/tests.py&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;test_scissors_vs_rock_right_wins
&lt;span class="w"&gt;    &lt;/span&gt;self.assertEqual&lt;span class="o"&gt;(&lt;/span&gt;result,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
AssertionError:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;
-&lt;span class="w"&gt; &lt;/span&gt;left
+&lt;span class="w"&gt; &lt;/span&gt;right


----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000s

FAILED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;failures&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Read that through carefully.
What does it tell us?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Our first test is still passing. That's good news, we haven't broken anything!&lt;/li&gt;
&lt;li&gt;Our second test fails, because it returns &lt;code&gt;"left"&lt;/code&gt; but we want it to return &lt;code&gt;"right"&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, what's the simplest possible change that would get this test passing?
Think about it for a minute or two.&lt;/p&gt;
&lt;p&gt;We're going to need some kind of &lt;em&gt;conditional logic&lt;/em&gt; here, because we return different results in different cases. 
However, we're supposed to be keeping things simple, so we don't want to leap all the way to a full implementation.
How about this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;def rps(left, right):
&lt;span class="gd"&gt;-    return &amp;quot;left&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+    return &amp;quot;left&amp;quot; if left == &amp;quot;rock&amp;quot; else &amp;quot;right&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest
..
----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000s

OK
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Alright, two down, let's commit what we've done so far:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Second test - scissors vs. rock&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;3a4c6bd&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Second&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletion&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We haven't created any new files since the last commit, so we can use the &lt;code&gt;-a&lt;/code&gt;/&lt;code&gt;--all&lt;/code&gt; flag to &lt;code&gt;git commit&lt;/code&gt; to include changes to all files, instead of needing to &lt;code&gt;git add&lt;/code&gt; anything.&lt;/p&gt;
&lt;h2&gt;Third time's the charm [6/10]&lt;/h2&gt;
&lt;p&gt;We've handled both of the cases involving rock and scissors, so let's try this one, scissors cut paper:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_scissors_vs_paper_left_wins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest
.F.
&lt;span class="o"&gt;======================================================================&lt;/span&gt;
FAIL:&lt;span class="w"&gt; &lt;/span&gt;test_scissors_vs_paper_left_wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;tests.RpsTests.test_scissors_vs_paper_left_wins&lt;span class="o"&gt;)&lt;/span&gt;
----------------------------------------------------------------------
Traceback&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;File&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path/to/rps-tdd/tests.py&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;test_scissors_vs_paper_left_wins
&lt;span class="w"&gt;    &lt;/span&gt;self.assertEqual&lt;span class="o"&gt;(&lt;/span&gt;result,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
AssertionError:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;
-&lt;span class="w"&gt; &lt;/span&gt;right
+&lt;span class="w"&gt; &lt;/span&gt;left


----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000s

FAILED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;failures&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So how can we get this to pass?
We can no longer rely on the value of the first parameter alone, because we have two &lt;em&gt;different&lt;/em&gt; outputs where &lt;code&gt;left&lt;/code&gt; is &lt;code&gt;"scissors"&lt;/code&gt;, so we're going to have to check the &lt;code&gt;right&lt;/code&gt; value.
For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;def rps(left, right):
&lt;span class="gd"&gt;-    return &amp;quot;left&amp;quot; if left == &amp;quot;rock&amp;quot; else &amp;quot;right&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+    return &amp;quot;right&amp;quot; if right == &amp;quot;rock&amp;quot; else &amp;quot;left&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest
...
----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000s

OK
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's a successful outcome.
However, it seems a bit odd to have &lt;code&gt;"right"&lt;/code&gt; on the left-hand side of the expression and &lt;code&gt;"left"&lt;/code&gt; on the right-hand side.
So now we can &lt;strong&gt;refactor&lt;/strong&gt;, keep the tests passing but change the implementation.
For example, how about:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;def rps(left, right):
&lt;span class="gd"&gt;-    return &amp;quot;right&amp;quot; if right == &amp;quot;rock&amp;quot; else &amp;quot;left&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+    return &amp;quot;left&amp;quot; if right != &amp;quot;rock&amp;quot; else &amp;quot;right&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest
...
----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000s

OK
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; a third key benefit of TDD here - we know that the code still does exactly what it's supposed to even though we've just changed the implementation. This allows us to confidently refactor towards cleaner code and higher quality.
Let's treat ourselves to a commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Third test - scissors vs. paper&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;ee7c6f0&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Third&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;paper
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletion&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Are there any puns about four? [7/10]&lt;/h2&gt;
&lt;p&gt;Let's flip the last condition to cover the other case involving paper and scissors:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_paper_vs_scissors_right_wins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest
F...
&lt;span class="o"&gt;======================================================================&lt;/span&gt;
FAIL:&lt;span class="w"&gt; &lt;/span&gt;test_paper_vs_scissors_right_wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;tests.RpsTests.test_paper_vs_scissors_right_wins&lt;span class="o"&gt;)&lt;/span&gt;
----------------------------------------------------------------------
Traceback&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;File&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path/to/rps-tdd/tests.py&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;test_paper_vs_scissors_right_wins
&lt;span class="w"&gt;    &lt;/span&gt;self.assertEqual&lt;span class="o"&gt;(&lt;/span&gt;result,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
AssertionError:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;
-&lt;span class="w"&gt; &lt;/span&gt;left
+&lt;span class="w"&gt; &lt;/span&gt;right


----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000s

FAILED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;failures&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At this point we can no longer check one parameter or the other, we must check &lt;em&gt;both&lt;/em&gt;.
For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;def rps(left, right):
&lt;span class="gd"&gt;-    return &amp;quot;left&amp;quot; if right != &amp;quot;rock&amp;quot; else &amp;quot;right&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+    return &amp;quot;left&amp;quot; if left != &amp;quot;paper&amp;quot; and right != &amp;quot;rock&amp;quot; else &amp;quot;right&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest
....
----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000s

OK
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;More progress, let's commit this too:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;Fourth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c34de33&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Fourth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;insertions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;deletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Gift-wrapped rock [8/10]&lt;/h2&gt;
&lt;p&gt;At this point you can probably see what's coming next; paper wraps rock:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_paper_vs_rock_left_wins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest
F....
&lt;span class="o"&gt;======================================================================&lt;/span&gt;
FAIL:&lt;span class="w"&gt; &lt;/span&gt;test_paper_vs_rock_left_wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;tests.RpsTests.test_paper_vs_rock_left_wins&lt;span class="o"&gt;)&lt;/span&gt;
----------------------------------------------------------------------
Traceback&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;File&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path/to/rps-tdd/tests.py&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;48&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;test_paper_vs_rock_left_wins
&lt;span class="w"&gt;    &lt;/span&gt;self.assertEqual&lt;span class="o"&gt;(&lt;/span&gt;result,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
AssertionError:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;
-&lt;span class="w"&gt; &lt;/span&gt;right
+&lt;span class="w"&gt; &lt;/span&gt;left


----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000s

FAILED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;failures&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Have a play with the implementation for a few minutes, see if you can come up with a way to write an implementation that passes all five tests.
Remember: write the code, call the shot, run the test, compare.&lt;/p&gt;
&lt;p&gt;For example, you might get to something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let's commit it and then flip to the last case:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Fifth test - paper vs. rock&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;21cdaf8&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Fifth&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletion&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_rock_vs_paper_right_wins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we've reached a point where, however we try to rearrange it, we're &lt;em&gt;forced&lt;/em&gt; to be explicit about all of the cases. For example, we might write:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;How does this compare with how you imagined implementing it to begin with?
Let's save it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Sixth test - rock vs. paper&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;54c00b7&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Sixth&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;paper
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Draw! [9/10]&lt;/h2&gt;
&lt;p&gt;So far we've assumed the two participants choose different values.
If you've played RPS, you'll know that's not always the case in real life - sometimes it's a draw.&lt;/p&gt;
&lt;p&gt;This brings us to the idea of &lt;em&gt;parameterised testing&lt;/em&gt; - generating tests based on canned data.
&lt;code&gt;unittest&lt;/code&gt; has built-in functionality to do this, named &lt;a href="https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests"&gt;&lt;code&gt;subTest&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_both_same_is_draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;both&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;both&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;both&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;both&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;both&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;draw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, run the tests, review the output then, if all of that makes sense, complete the implementation. Maybe something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;def rps(left, right):
&lt;span class="gi"&gt;+    if left == right:&lt;/span&gt;
&lt;span class="gi"&gt;+        return &amp;quot;draw&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    return (
&lt;span class="w"&gt; &lt;/span&gt;        &amp;quot;left&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once everything's passing and you're happy with your implementation, make the final commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Handle the draw cases&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;95ed150&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Handle&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;draw&lt;span class="w"&gt; &lt;/span&gt;cases
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="exercises"&gt;Exercises [10/10]&lt;/h2&gt;
&lt;p&gt;Practice makes perfect! Here are some additional exercises you can run through:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Repeat the process, but tackle the pairs in a different order. What impact does the order have on how and when your implementation gains complexity? Do you end up with a different implementation?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extend your implementation for &lt;a href="https://en.wikipedia.org/wiki/Rock_paper_scissors#Additional_weapons"&gt;additional weapons&lt;/a&gt; (e.g. Rock Paper Scissors Lizard Spock). How easy or hard is this?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Advanced&lt;/strong&gt; - read about the &lt;em&gt;"open-closed principle"&lt;/em&gt;, &lt;a href="https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle"&gt;OCP&lt;/a&gt;. Can you refactor your code such that adding more weapons doesn't mean a change to the &lt;code&gt;rps&lt;/code&gt; function?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test-drive out some validation - what should your code do if either or both of the inputs aren't recognised shapes? If you decide to throw an error, note that per &lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaises"&gt;the docs&lt;/a&gt; you need to use a &lt;em&gt;context manager&lt;/em&gt; to handle the exception: &lt;/p&gt;
&lt;p&gt;&lt;code&gt;with self.assertRaises(ValueError):
    rps("bananas", "apples")&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Refactor the tests to group the test cases into three parameterised tests: one for &lt;code&gt;"left"&lt;/code&gt;; one for &lt;code&gt;"right"&lt;/code&gt;; and one for &lt;code&gt;"draw"&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run through the exercise with a different test framework, e.g. &lt;a href="https://docs.pytest.org/en/stable/index.html"&gt;pytest&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Once you're ready to move on&lt;/strong&gt;, check out &lt;a href="https://blog.jonrshar.pe/2024/Aug/17/python-tdd-ohm.html"&gt;the next article&lt;/a&gt; in this series where we'll cover testing of an API and discuss more about how tests drive design.&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="development"></category><category term="python"></category><category term="tdd"></category><category term="xp"></category></entry><entry><title>JS TDD FTW (redux)</title><link href="https://blog.jonrshar.pe/2024/Apr/09/js-tdd-ftw-redux.html" rel="alternate"></link><published>2024-04-09T23:40:00+01:00</published><updated>2024-04-09T23:40:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2024-04-09:/2024/Apr/09/js-tdd-ftw-redux.html</id><summary type="html">&lt;p&gt;Test-driven JavaScript development done right - part 1, again&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: this was originally published as &lt;a href="https://blog.jonrshar.pe/2020/Aug/31/js-tdd-ftw.html"&gt;JS TDD FTW&lt;/a&gt;, using Jest, but has been rewritten to use the Node.js test runner. The principles and process are exactly the same.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the key Extreme Programming (&lt;a href="http://wiki.c2.com/?ExtremeProgramming"&gt;XP&lt;/a&gt;) engineering practices is test-driven development (TDD), usually expressed as repeatedly following this simple, three-step process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Red&lt;/strong&gt; - write a failing test that describes the behaviour you want;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Green&lt;/strong&gt; - write the simplest possible code to make the test pass; and&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refactor&lt;/strong&gt; - clean up your code without breaking the tests.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I was recently asked if I knew of a good TDD intro for people who were comfortable with JavaScript but hadn't done much testing, so I did some research.
There are lots of examples of testing and TDD out there, but often: tied to specific frameworks (e.g. React); with unclear prerequisites; and even showing poor testing practices.
So below I'm going to give a proper example of vanilla JavaScript TDD done &lt;em&gt;"the right way"&lt;/em&gt;, sprinkling some bonus command line and git practice throughout.&lt;/p&gt;
&lt;h3&gt;Requirements&lt;/h3&gt;
&lt;p&gt;I've aimed this content at more junior developers, so there are more explanations than all readers will need, but anyone new to testing and TDD should find something to take from it.
We'll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;*nix command line: already provided on macOS and Linux; if you're using Windows try &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/about"&gt;WSL&lt;/a&gt; or &lt;a href="https://gitforwindows.org/"&gt;Git BASH&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/"&gt;Node&lt;/a&gt; (minimum v16.17; run &lt;code&gt;node -v&lt;/code&gt; to check) and npm; and&lt;/li&gt;
&lt;li&gt;Familiarity with ES6 JavaScript syntax (specifically arrow functions).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We also need something to implement.
&lt;a href="https://en.wikipedia.org/wiki/Rock_paper_scissors"&gt;Rock Paper Scissors&lt;/a&gt; (or just RPS) is a simple playground game that takes some inputs (the shapes that the players present) and gives a single output (the outcome), which makes it a good fit for a simple function to test drive.
If you're not familiar with the rules, read the linked Wikipedia article before continuing.&lt;/p&gt;
&lt;p&gt;Before we get into the TDD process, think about what the code for an implementation of RPS might look like.
Don't write any code yet (we don't have the failing tests to make us do that!) but imagine a function - what parameters would it accept?
What would it return?
We're expecting different outputs for different inputs, which implies some conditional logic - what conditions do you think would be involved?
Note your ideas down, we'll revisit them later.&lt;/p&gt;
&lt;p&gt;As we go through, please carefully &lt;em&gt;read everything&lt;/em&gt;.
I'd recommend &lt;em&gt;typing the code&lt;/em&gt; rather than copy-pasting, especially if you're a new developer; it's good practice to build your muscle memory.&lt;/p&gt;
&lt;h2&gt;Setup [1/10]&lt;/h2&gt;
&lt;p&gt;Let's get up and running.
Starting in your working directory (e.g. I use &lt;code&gt;~/workspace&lt;/code&gt;), run the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;rps-tdd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--allow-empty&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Initial commit&amp;#39;&lt;/span&gt;
Initialized&lt;span class="w"&gt; &lt;/span&gt;empty&lt;span class="w"&gt; &lt;/span&gt;Git&lt;span class="w"&gt; &lt;/span&gt;repository&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd/.git/
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;root-commit&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Initial&lt;span class="w"&gt; &lt;/span&gt;commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;By chaining multiple commands using &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; (&lt;em&gt;"and"&lt;/em&gt;, assuming the previous commands all succeeded), this will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a new directory named &lt;code&gt;rps-tdd/&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Switch into it (&lt;code&gt;$_&lt;/code&gt; references the argument to the last command, see e.g. &lt;a href="https://stackoverflow.com/q/30154694/3001761"&gt;this SO question&lt;/a&gt;);&lt;/li&gt;
&lt;li&gt;Initialise a new git repository; and&lt;/li&gt;
&lt;li&gt;Create an empty initial commit (&lt;code&gt;--allow-empty&lt;/code&gt; lets us create this first commit without having any content, and &lt;code&gt;-m&lt;/code&gt; lets us supply the commit message on the command line).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now we need a basic npm package:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;package.json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Create NPM package&amp;#39;&lt;/span&gt;
Wrote&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd/package.json:

&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rps-tdd&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;version&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1.0.0&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;main&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;index.js&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scripts&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;keywords&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;author&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;license&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ISC&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Create&lt;span class="w"&gt; &lt;/span&gt;NPM&lt;span class="w"&gt; &lt;/span&gt;package
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;package.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creates a basic &lt;code&gt;package.json&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Adds that to our repo; and&lt;/li&gt;
&lt;li&gt;Makes another commit.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By default, NPM creates a test script that will throw an error, as we saw above:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you run this using &lt;code&gt;npm test&lt;/code&gt; (alternatively &lt;code&gt;npm run test&lt;/code&gt; or even just &lt;code&gt;npm t&lt;/code&gt;) you see the result:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error: no test specified&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;

Error:&lt;span class="w"&gt; &lt;/span&gt;no&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;specified
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;Test&lt;span class="w"&gt; &lt;/span&gt;failed.&lt;span class="w"&gt;  &lt;/span&gt;See&lt;span class="w"&gt; &lt;/span&gt;above&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;details.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Running tests [2/10]&lt;/h2&gt;
&lt;p&gt;Next, we're going to need something to run our tests.
There are loads of options here (I've used &lt;a href="https://jasmine.github.io/"&gt;Jasmine&lt;/a&gt;, &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt;, &lt;a href="https://mochajs.org/"&gt;Mocha&lt;/a&gt;, &lt;a href="https://www.npmjs.com/package/tape"&gt;tape&lt;/a&gt;, ...) but we're going to start with one that's built right into your runtime: &lt;a href="https://nodejs.org/api/test.html"&gt;Node test runner&lt;/a&gt;.
We can update &lt;code&gt;package.json&lt;/code&gt; to set this to be our test command.&lt;/p&gt;
&lt;p&gt;Edit &lt;code&gt;package.json&lt;/code&gt; to update the script to &lt;code&gt;"test": "node --test"&lt;/code&gt;, using either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an editor or IDE of your choice; or&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm pkg set scripts.test='node --test'&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;to use the test runner. Then run the tests again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@0.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;--test

ℹ&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;suites&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;pass&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;fail&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;cancelled&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;skipped&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;todo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;duration_ms&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.717458
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This seems happy, but reading the output we can see there are no tests so far.
Let's start with something completely trivial to make sure everything is working; add the following to a file named &lt;code&gt;index.test.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node:assert/strict&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node:test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should work&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now run the tests a third time:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@0.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;--test

✔&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;work&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.672875ms&lt;span class="o"&gt;)&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;suites&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;pass&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;fail&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;cancelled&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;skipped&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;todo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;duration_ms&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;43&lt;/span&gt;.945125
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We have one passing test!
So what does that test &lt;em&gt;do&lt;/em&gt;, what's going on there?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We call the &lt;code&gt;it&lt;/code&gt; function (imported from the test runner) to &lt;strong&gt;register a test&lt;/strong&gt;. We pass it two things:&lt;ul&gt;
&lt;li&gt;The name of the test, as a string (you can see this name in the output, too). In this style of testing we use the function name along with the test as one sentence describing our expectation: &lt;em&gt;"it should work"&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;The body of the test, as a function. Right now we're just &lt;em&gt;registering&lt;/em&gt; the test, the runner will call that function for us when it runs the test.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Within the test body, we &lt;strong&gt;establish our expectations&lt;/strong&gt;. What exactly do we think should happen? I've split this into three sections:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Arrange&lt;/strong&gt; (sometimes known as &lt;em&gt;"given"&lt;/em&gt;) - set up the preconditions for our test, in this case two initial values.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Act&lt;/strong&gt; (or &lt;em&gt;"when"&lt;/em&gt;) - do some work, in this case adding them together. This is what we're actually testing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assert&lt;/strong&gt; (or &lt;em&gt;"then"&lt;/em&gt;) - make sure that the work was done correctly. The &lt;code&gt;assert&lt;/code&gt; object is also imported from Node, from the &lt;a href="https://nodejs.org/api/assert.html"&gt;&lt;code&gt;assert&lt;/code&gt; module&lt;/a&gt;; it provides a method to compare the value we receive (often referred to as &lt;em&gt;"actual"&lt;/em&gt;) with what we think it should be (&lt;em&gt;"expected"&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If we had an inaccurate expectation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node:assert/strict&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node:test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should work&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;it would tell us exactly what the problem was:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@0.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;--test

✖&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;work&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.557708ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;AssertionError&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;ERR_ASSERTION&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Expected&lt;span class="w"&gt; &lt;/span&gt;values&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;strictly&lt;span class="w"&gt; &lt;/span&gt;equal:

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;TestContext.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/rps-tdd/index.test.js:10:10&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.runInAsyncScope&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:async_hooks:203:9&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:631:25&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.start&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:542:17&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;startSubtest&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/harness:214:17&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;generatedMessage:&lt;span class="w"&gt; &lt;/span&gt;true,
&lt;span class="w"&gt;    &lt;/span&gt;code:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ERR_ASSERTION&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;actual:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;expected:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;operator:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;strictEqual&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

ℹ&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;suites&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;pass&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;fail&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;cancelled&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;skipped&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;todo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;duration_ms&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;44&lt;/span&gt;.8275

✖&lt;span class="w"&gt; &lt;/span&gt;failing&lt;span class="w"&gt; &lt;/span&gt;tests:

&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;index.test.js:4:1
✖&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;work&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.557708ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;AssertionError&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;ERR_ASSERTION&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Expected&lt;span class="w"&gt; &lt;/span&gt;values&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;strictly&lt;span class="w"&gt; &lt;/span&gt;equal:

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;TestContext.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/rps-tdd/index.test.js:10:10&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.runInAsyncScope&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:async_hooks:203:9&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:631:25&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.start&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:542:17&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;startSubtest&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/harness:214:17&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;generatedMessage:&lt;span class="w"&gt; &lt;/span&gt;true,
&lt;span class="w"&gt;    &lt;/span&gt;code:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ERR_ASSERTION&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;actual:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;expected:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;operator:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;strictEqual&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Protip&lt;/strong&gt;: always read the outputs carefully! Sometimes a test fails for an unexpected reason, which usually tells you something interesting.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, we're happy things are working so far.&lt;/p&gt;
&lt;h2&gt;A failing test [3/10]&lt;/h2&gt;
&lt;p&gt;Let's start some actual TDD, and write our first failing test. Replace the content of &lt;code&gt;index.test.js&lt;/code&gt; with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node:assert/strict&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node:test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock, paper, scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should say left wins for rock vs. scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note I've introduced another function from the test runner, &lt;code&gt;describe&lt;/code&gt;.
This registers a &lt;em&gt;group&lt;/em&gt; of tests, usually referred to as a &lt;em&gt;"suite"&lt;/em&gt;.
Like &lt;code&gt;it&lt;/code&gt; it takes a name and a function, then our individual tests are registered inside that function.&lt;/p&gt;
&lt;p&gt;Our first test is that, given that &lt;code&gt;left&lt;/code&gt; is &lt;code&gt;"rock"&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt; is &lt;code&gt;"scissors"&lt;/code&gt; (&lt;em&gt;"Arrange"&lt;/em&gt;), when the shapes are compared (&lt;em&gt;"Act"&lt;/em&gt;) , then the winner should be &lt;code&gt;"left"&lt;/code&gt; (&lt;em&gt;"Assert"&lt;/em&gt;) because rock blunts scissors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; one key benefit of TDD here - we can try out how we should interact with our code (its &lt;em&gt;"interface"&lt;/em&gt;) before we've even written any.
Maybe it should return something other than a string, for example?
We can have that discussion now, while it's just a matter of changing our minds rather than the code.&lt;/p&gt;
&lt;p&gt;Before we run the first test, &lt;a href="https://markhneedham.com/blog/2010/07/28/tdd-call-your-shots/"&gt;&lt;em&gt;"call the shot"&lt;/em&gt;&lt;/a&gt; - make a prediction of what the test result will be, pass or fail.
If you think the test will fail, &lt;strong&gt;why&lt;/strong&gt;; will the &lt;code&gt;expect&lt;/code&gt;ation be unmet (and what value do you think you'll get instead) or will something else go wrong?
This is really good practice for &lt;em&gt;"playing computer"&lt;/em&gt; (modelling the behaviour of the code in your head) and you can write your guess down (or say it out loud if you're pairing) to keep yourself honest.
Now let's run it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tdd&lt;/span&gt;&lt;span class="mf"&gt;@0.1.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;

&lt;span class="err"&gt;▶&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;✖&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.328041&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ReferenceError&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;defined&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TestContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;anonymous&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tdd&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runInAsyncScope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;async_hooks&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;203&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test_runner&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;631&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test_runner&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;542&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test_runner&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;946&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;71&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;per_context&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;primordials&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;487&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;82&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Promise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;anonymous&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SafePromise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;per_context&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;primordials&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;455&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;per_context&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;primordials&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;487&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;anonymous&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="err"&gt;▶&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.18&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;suites&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cancelled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;skipped&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;duration_ms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;45.090125&lt;/span&gt;

&lt;span class="err"&gt;✖&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;failing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="err"&gt;✖&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.328041&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ReferenceError&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;defined&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TestContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;anonymous&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tdd&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runInAsyncScope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;async_hooks&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;203&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test_runner&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;631&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test_runner&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;542&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test_runner&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;946&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;71&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;per_context&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;primordials&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;487&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;82&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Promise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;anonymous&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SafePromise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;per_context&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;primordials&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;455&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;per_context&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;primordials&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;487&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;anonymous&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;...were you right?&lt;/p&gt;
&lt;h2&gt;The simplest possible change [4/10]&lt;/h2&gt;
&lt;p&gt;As you may have guessed, this fails because &lt;code&gt;rps&lt;/code&gt; &lt;em&gt;doesn't exist yet&lt;/em&gt;.
Let's make the simplest possible change that will at least change the error we're receiving; define the function.
At this stage we could &lt;code&gt;require&lt;/code&gt; the function from another file, but let's keep things simple for now; add the following to the top of &lt;code&gt;index.test.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@0.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;--test

▶&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt;  &lt;/span&gt;✖&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.634458ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;AssertionError&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;ERR_ASSERTION&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Expected&lt;span class="w"&gt; &lt;/span&gt;values&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;strictly&lt;span class="w"&gt; &lt;/span&gt;equal:
&lt;span class="w"&gt;    &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;actual&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;expected

&lt;span class="w"&gt;    &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;undefined
&lt;span class="w"&gt;    &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;TestContext.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/rps-tdd/index.test.js:13:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.runInAsyncScope&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:async_hooks:203:9&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:631:25&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.start&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:542:17&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;node:internal/test_runner/test:946:71
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;node:internal/per_context/primordials:487:82
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;Promise&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&amp;lt;anonymous&amp;gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;SafePromise&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/per_context/primordials:455:29&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;node:internal/per_context/primordials:487:9
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Array.map&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&amp;lt;anonymous&amp;gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;generatedMessage:&lt;span class="w"&gt; &lt;/span&gt;true,
&lt;span class="w"&gt;      &lt;/span&gt;code:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ERR_ASSERTION&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;actual:&lt;span class="w"&gt; &lt;/span&gt;undefined,
&lt;span class="w"&gt;      &lt;/span&gt;expected:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;operator:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;strictEqual&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

▶&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.574708ms&lt;span class="o"&gt;)&lt;/span&gt;

ℹ&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;suites&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;pass&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;fail&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;cancelled&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;skipped&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;todo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;duration_ms&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;47&lt;/span&gt;.723458

✖&lt;span class="w"&gt; &lt;/span&gt;failing&lt;span class="w"&gt; &lt;/span&gt;tests:

&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;index.test.js:7:3
✖&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.634458ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;AssertionError&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;ERR_ASSERTION&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Expected&lt;span class="w"&gt; &lt;/span&gt;values&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;strictly&lt;span class="w"&gt; &lt;/span&gt;equal:
&lt;span class="w"&gt;  &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;actual&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;expected

&lt;span class="w"&gt;  &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;undefined
&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;TestContext.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/rps-tdd/index.test.js:13:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.runInAsyncScope&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:async_hooks:203:9&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:631:25&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.start&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:542:17&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;node:internal/test_runner/test:946:71
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;node:internal/per_context/primordials:487:82
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;Promise&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&amp;lt;anonymous&amp;gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;SafePromise&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/per_context/primordials:455:29&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;node:internal/per_context/primordials:487:9
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Array.map&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&amp;lt;anonymous&amp;gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;generatedMessage:&lt;span class="w"&gt; &lt;/span&gt;true,
&lt;span class="w"&gt;    &lt;/span&gt;code:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ERR_ASSERTION&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;actual:&lt;span class="w"&gt; &lt;/span&gt;undefined,
&lt;span class="w"&gt;    &lt;/span&gt;expected:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;operator:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;strictEqual&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Our test still doesn't pass, but at least we've changed the error message - we're now reaching the actual expectation, instead of crashing when we try to call the function.
So let's make the simplest possible change that should get this passing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tdd&lt;/span&gt;&lt;span class="mf"&gt;@0.1.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;

&lt;span class="err"&gt;▶&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;✔&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.195792&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;▶&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.037958&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;suites&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cancelled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;skipped&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;duration_ms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;42.535125&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Great! This calls for a celebratory commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;-AN&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-am&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;First test - rock vs. scissors&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;First&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;index.test.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; another key benefit of TDD here - it tells you when you're done.
Once the tests are passing, the implementation meets the current requirements.&lt;/p&gt;
&lt;h2&gt;The difficult second test [5/10]&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;"But wait"&lt;/em&gt;, you might be thinking, &lt;em&gt;"that's pointless, it doesn't &lt;strong&gt;do&lt;/strong&gt; anything!"&lt;/em&gt; And to an extent, that's true; our function just returns a hard-coded string. But let's think about what else has happened:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We've decided on an interface for our function, what it's going to receive and return;&lt;/li&gt;
&lt;li&gt;We've proved out a test setup that lets us make assertions on the behaviour of that function; and&lt;/li&gt;
&lt;li&gt;We've created the simplest possible implementation for the requirements we've expressed through tests so far, making our code very robust.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So let's build on that foundation; flip the shapes around to change the output so we can expect the test to fail.
Add the following into the &lt;code&gt;describe&lt;/code&gt; callback in &lt;code&gt;index.test.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should say right wins for scissors vs. rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that the &lt;em&gt;"Act"&lt;/em&gt; is the same, but the &lt;em&gt;"Arrange"&lt;/em&gt; and &lt;em&gt;"Assert"&lt;/em&gt; have changed. Call the shot, then run the test again to see if you were correct:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t&lt;span class="w"&gt;                                                      &lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@0.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;--test

▶&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt;  &lt;/span&gt;✔&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.512792ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;✖&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.268042ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;AssertionError&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;ERR_ASSERTION&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Expected&lt;span class="w"&gt; &lt;/span&gt;values&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;strictly&lt;span class="w"&gt; &lt;/span&gt;equal:
&lt;span class="w"&gt;    &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;actual&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;expected

&lt;span class="w"&gt;    &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;TestContext.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/rps-tdd/index.test.js:24:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.runInAsyncScope&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:async_hooks:203:9&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:631:25&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Suite.processPendingSubtests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:374:18&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.postRun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:715:19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:673:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;async&lt;span class="w"&gt; &lt;/span&gt;Promise.all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;index&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;async&lt;span class="w"&gt; &lt;/span&gt;Suite.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:948:7&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;async&lt;span class="w"&gt; &lt;/span&gt;startSubtest&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/harness:214:3&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;generatedMessage:&lt;span class="w"&gt; &lt;/span&gt;true,
&lt;span class="w"&gt;      &lt;/span&gt;code:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ERR_ASSERTION&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;actual:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;expected:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;operator:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;strictEqual&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

▶&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.778583ms&lt;span class="o"&gt;)&lt;/span&gt;

ℹ&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;suites&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;pass&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;fail&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;cancelled&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;skipped&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;todo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;duration_ms&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;48&lt;/span&gt;.376417

✖&lt;span class="w"&gt; &lt;/span&gt;failing&lt;span class="w"&gt; &lt;/span&gt;tests:

&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;index.test.js:18:3
✖&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.268042ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;AssertionError&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;ERR_ASSERTION&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Expected&lt;span class="w"&gt; &lt;/span&gt;values&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;strictly&lt;span class="w"&gt; &lt;/span&gt;equal:
&lt;span class="w"&gt;  &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;actual&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;expected

&lt;span class="w"&gt;  &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;TestContext.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/rps-tdd/index.test.js:24:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.runInAsyncScope&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:async_hooks:203:9&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:631:25&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Suite.processPendingSubtests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:374:18&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.postRun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:715:19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:673:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;async&lt;span class="w"&gt; &lt;/span&gt;Promise.all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;index&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;async&lt;span class="w"&gt; &lt;/span&gt;Suite.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:948:7&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;async&lt;span class="w"&gt; &lt;/span&gt;startSubtest&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/harness:214:3&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;generatedMessage:&lt;span class="w"&gt; &lt;/span&gt;true,
&lt;span class="w"&gt;    &lt;/span&gt;code:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ERR_ASSERTION&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;actual:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;expected:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;operator:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;strictEqual&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Read that through carefully. What does it tell us?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Our first test is still passing. That's good news, we haven't broken anything!&lt;/li&gt;
&lt;li&gt;Our second test fails, because it returns &lt;code&gt;"left"&lt;/code&gt; but we want it to return &lt;code&gt;"right"&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, what's the simplest possible change that would get this test passing?
Think about it for a minute or two.&lt;/p&gt;
&lt;p&gt;We're going to need some kind of &lt;em&gt;conditional logic&lt;/em&gt; here, because we return different results in different cases.
However, we're supposed to be keeping things simple, so we don't want to leap all the way to a full implementation.
How about this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@0.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;--test

▶&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt;  &lt;/span&gt;✔&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.629167ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;✔&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.055334ms&lt;span class="o"&gt;)&lt;/span&gt;
▶&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.677833ms&lt;span class="o"&gt;)&lt;/span&gt;

ℹ&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;suites&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;pass&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;fail&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;cancelled&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;skipped&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;todo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;duration_ms&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;46&lt;/span&gt;.227791
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Alright, two down, let's commit what we've done so far:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-am&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Second test - scissors vs. rock&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Second&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We haven't created any new files since the last commit, so we can use the &lt;code&gt;-a&lt;/code&gt;/&lt;code&gt;--all&lt;/code&gt; flag to &lt;code&gt;git commit&lt;/code&gt; to include changes to all files, instead of needing to &lt;code&gt;git add&lt;/code&gt; anything.&lt;/p&gt;
&lt;h2&gt;Third time's the charm [6/10]&lt;/h2&gt;
&lt;p&gt;We've handled both of the cases involving rock and scissors, so let's try this one, scissors cut paper:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should say left wins for scissors vs. paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t&lt;span class="w"&gt;         &lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@0.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;--test

▶&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt;  &lt;/span&gt;✔&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.456666ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;✔&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.043917ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;✖&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.82575ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;AssertionError&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;ERR_ASSERTION&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Expected&lt;span class="w"&gt; &lt;/span&gt;values&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;strictly&lt;span class="w"&gt; &lt;/span&gt;equal:
&lt;span class="w"&gt;    &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;actual&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;expected

&lt;span class="w"&gt;    &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;TestContext.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/rps-tdd/index.test.js:33:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.runInAsyncScope&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:async_hooks:203:9&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:631:25&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Suite.processPendingSubtests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:374:18&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.postRun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:715:19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:673:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;async&lt;span class="w"&gt; &lt;/span&gt;Suite.processPendingSubtests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:374:7&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;generatedMessage:&lt;span class="w"&gt; &lt;/span&gt;true,
&lt;span class="w"&gt;      &lt;/span&gt;code:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ERR_ASSERTION&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;actual:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;expected:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;operator:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;strictEqual&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

▶&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.268291ms&lt;span class="o"&gt;)&lt;/span&gt;

ℹ&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;suites&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;pass&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;fail&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;cancelled&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;skipped&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;todo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;duration_ms&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;47&lt;/span&gt;.135125

✖&lt;span class="w"&gt; &lt;/span&gt;failing&lt;span class="w"&gt; &lt;/span&gt;tests:

&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;index.test.js:27:3
✖&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.82575ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;AssertionError&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;ERR_ASSERTION&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Expected&lt;span class="w"&gt; &lt;/span&gt;values&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;strictly&lt;span class="w"&gt; &lt;/span&gt;equal:
&lt;span class="w"&gt;  &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;actual&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;expected

&lt;span class="w"&gt;  &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;TestContext.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/rps-tdd/index.test.js:33:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.runInAsyncScope&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:async_hooks:203:9&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:631:25&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Suite.processPendingSubtests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:374:18&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.postRun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:715:19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:673:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;async&lt;span class="w"&gt; &lt;/span&gt;Suite.processPendingSubtests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:374:7&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;generatedMessage:&lt;span class="w"&gt; &lt;/span&gt;true,
&lt;span class="w"&gt;    &lt;/span&gt;code:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ERR_ASSERTION&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;actual:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;expected:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;operator:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;strictEqual&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So how can we get this to pass?
We can no longer rely on the value of the first parameter alone, because we have two &lt;em&gt;different&lt;/em&gt; outputs where &lt;code&gt;left&lt;/code&gt; is &lt;code&gt;"scissors"&lt;/code&gt;, so we're going to have to also check the &lt;code&gt;right&lt;/code&gt; value.
For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tdd&lt;/span&gt;&lt;span class="mf"&gt;@0.1.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;

&lt;span class="err"&gt;▶&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;✔&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.214833&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;✔&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.044542&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;✔&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.044708&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;▶&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.210625&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;suites&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cancelled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;skipped&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;duration_ms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;43.86725&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's a successful outcome, but our code is a bit of a mess; a conditional expression inside another conditional expression isn't very clear and we've repeated the &lt;em&gt;"magic value"&lt;/em&gt; &lt;code&gt;"left"&lt;/code&gt; twice.
So now we can &lt;strong&gt;refactor&lt;/strong&gt;, keep the tests passing but change the implementation.
For example, how about:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tdd&lt;/span&gt;&lt;span class="mf"&gt;@0.1.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;

&lt;span class="err"&gt;▶&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;✔&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.215584&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;✔&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.043833&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;✔&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.044292&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;▶&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.188125&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;suites&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cancelled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;skipped&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ℹ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;duration_ms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;43.331375&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; a third key benefit of TDD here - we know that the code still does exactly what it's supposed to even though we've just changed the implementation.
This allows us to confidently refactor towards cleaner code and higher quality.&lt;/p&gt;
&lt;p&gt;Let's treat ourselves to a commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-am&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Third test - scissors vs. paper&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Third&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;paper
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Are there any puns about four? [7/10]&lt;/h2&gt;
&lt;p&gt;Let's flip the last condition to cover the other case involving paper and scissors:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should say right wins for paper vs. scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t&lt;span class="w"&gt;                                           &lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@0.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;--test

▶&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt;  &lt;/span&gt;✔&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.240583ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;✔&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.04775ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;✔&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.045666ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;✔&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.123792ms&lt;span class="o"&gt;)&lt;/span&gt;
▶&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.43025ms&lt;span class="o"&gt;)&lt;/span&gt;

ℹ&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;suites&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;pass&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;fail&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;cancelled&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;skipped&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;todo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;duration_ms&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;43&lt;/span&gt;.704917
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;...huh. &lt;code&gt;left&lt;/code&gt; isn't &lt;code&gt;"rock"&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt; isn't &lt;code&gt;"paper"&lt;/code&gt;, so it returns &lt;code&gt;"right"&lt;/code&gt;, which is the answer we wanted. 
This doesn't drive our implementation forward, but it is the behaviour we want, so let's commit this too:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-am&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Fourth test - paper vs. scissors&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Fourth&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Gift-wrapped rock [8/10]&lt;/h2&gt;
&lt;p&gt;At this point you can probably see what's coming next; paper wraps rock:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should say left wins for paper vs. rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t&lt;span class="w"&gt;                                            &lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@0.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;--test

▶&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt;  &lt;/span&gt;✔&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.233458ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;✔&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.045917ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;✔&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.043667ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;✔&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.1115ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;✖&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.787542ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;AssertionError&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;ERR_ASSERTION&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Expected&lt;span class="w"&gt; &lt;/span&gt;values&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;strictly&lt;span class="w"&gt; &lt;/span&gt;equal:
&lt;span class="w"&gt;    &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;actual&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;expected

&lt;span class="w"&gt;    &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;TestContext.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/rps-tdd/index.test.js:53:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.runInAsyncScope&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:async_hooks:203:9&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:631:25&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Suite.processPendingSubtests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:374:18&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.postRun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:715:19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:673:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;async&lt;span class="w"&gt; &lt;/span&gt;Suite.processPendingSubtests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:374:7&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;generatedMessage:&lt;span class="w"&gt; &lt;/span&gt;true,
&lt;span class="w"&gt;      &lt;/span&gt;code:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ERR_ASSERTION&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;actual:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;expected:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;operator:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;strictEqual&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

▶&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.216417ms&lt;span class="o"&gt;)&lt;/span&gt;

ℹ&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;suites&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;pass&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;fail&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;cancelled&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;skipped&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;todo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
ℹ&lt;span class="w"&gt; &lt;/span&gt;duration_ms&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;46&lt;/span&gt;.239333

✖&lt;span class="w"&gt; &lt;/span&gt;failing&lt;span class="w"&gt; &lt;/span&gt;tests:

&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;index.test.js:47:3
✖&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.787542ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;AssertionError&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;ERR_ASSERTION&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Expected&lt;span class="w"&gt; &lt;/span&gt;values&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;strictly&lt;span class="w"&gt; &lt;/span&gt;equal:
&lt;span class="w"&gt;  &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;actual&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;expected

&lt;span class="w"&gt;  &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;TestContext.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/rps-tdd/index.test.js:53:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.runInAsyncScope&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:async_hooks:203:9&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:631:25&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Suite.processPendingSubtests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:374:18&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.postRun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:715:19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:673:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;async&lt;span class="w"&gt; &lt;/span&gt;Suite.processPendingSubtests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/test_runner/test:374:7&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;generatedMessage:&lt;span class="w"&gt; &lt;/span&gt;true,
&lt;span class="w"&gt;    &lt;/span&gt;code:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ERR_ASSERTION&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;actual:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;expected:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;operator:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;strictEqual&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Alright, this time we do get a failure again.
Have a play with the implementation for a few minutes, see if you can come up with a way to write an implementation of the form &lt;code&gt;&amp;lt;condition&amp;gt; ? "left" : "right";&lt;/code&gt; that passes all five tests.
Remember: write the code, call the shot, run the test, compare.&lt;/p&gt;
&lt;p&gt;For example, you might get to something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let's commit it and then flip to the last case:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-am&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Fifth test - paper vs. rock&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;bash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Fifth&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletion&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should say right wins for rock vs. paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we've reached a point where, however we try to rearrange it, we're &lt;em&gt;forced&lt;/em&gt; to be explicit about all of the cases. For example, we might write:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This probably looks a lot like what you imagined to begin with. Let's save it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-am&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Sixth test - rock vs. paper&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Sixth&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;paper
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletion&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Draw! [9/10]&lt;/h2&gt;
&lt;p&gt;So far we've assumed the two participants choose different values.
If you've played RPS, you'll know that's not always the case in real life - sometimes it's a draw.&lt;/p&gt;
&lt;p&gt;This brings us to the idea of &lt;em&gt;parameterised testing&lt;/em&gt; - generating tests based on canned data. We can do it with an array and &lt;code&gt;forEach&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;both&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`should say draw for &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;both&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; vs. &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;both&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;both&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;both&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;draw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, run the tests, review the output then, if all of that makes sense, complete the implementation. Maybe something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;draw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once everything's passing and you're happy with your implementation, make the final commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-am&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Handle the draw cases&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Handle&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;draw&lt;span class="w"&gt; &lt;/span&gt;cases
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's it!
We've just test-driven an implementation of RPS, the right way.
Reflect on the exercise - how does the implementation compare to what you'd initially imagined?
What felt good or bad about the process?&lt;/p&gt;
&lt;p&gt;You can see my copy of this exercise at &lt;a href="https://github.com/textbook/rps-tdd-redux"&gt;https://github.com/textbook/rps-tdd-redux&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="exercises"&gt;Exercises [10/10]&lt;/h2&gt;
&lt;p&gt;Practice makes perfect! Here are some additional exercises you can run through:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Repeat the process, but tackle the pairs in a different order. What impact does the order have on how and when your implementation gains complexity? Do you end up with a different implementation?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extend your implementation for &lt;a href="https://en.wikipedia.org/wiki/Rock_paper_scissors#Additional_weapons"&gt;additional weapons&lt;/a&gt; (e.g. Rock Paper Scissors Lizard Spock). How easy or hard is this?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Advanced&lt;/strong&gt; - read about the &lt;em&gt;"open-closed principle"&lt;/em&gt;, &lt;a href="https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle"&gt;OCP&lt;/a&gt;. Can you refactor your code such that adding more weapons doesn't mean a change to the &lt;code&gt;rps&lt;/code&gt; function?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test-drive out some validation - what should your code do if either or both of the inputs aren't recognised shapes? If you decide to throw an error, note that per &lt;a href="https://nodejs.org/api/assert.html#assertthrowsfn-error-message"&gt;the docs&lt;/a&gt; you need to pass a function to defer execution: &lt;/p&gt;
&lt;p&gt;&lt;code&gt;assert.throws(() =&amp;gt; rps("bananas", 123));&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;otherwise the error's thrown too early and the runner can't handle it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Refactor the tests to group the test cases into three parameterised tests: one for &lt;code&gt;"left"&lt;/code&gt;; one for &lt;code&gt;"right"&lt;/code&gt;; and one for &lt;code&gt;"draw"&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run through the exercise from the beginning, but using &lt;code&gt;npm t -- --watch&lt;/code&gt; instead of just &lt;code&gt;npm t&lt;/code&gt; (note the extra &lt;code&gt;--&lt;/code&gt;, otherwise the &lt;code&gt;--watch&lt;/code&gt; argument gets passed to NPM rather than the test runner).&lt;/p&gt;
&lt;p&gt;This will set up the runner to watch your files and re-run the tests if anything changes. Arrange your screen so you can see these outputs while you're writing your code, and leave it running the whole time (if this means you can't make commits, don't worry about it). What impact does that immediate and continuous feedback have on your experience of working on the code?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run through the exercise with a different test framework, e.g.:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://mochajs.org/"&gt;Mocha&lt;/a&gt; with &lt;a href="https://www.chaijs.com/"&gt;Chai&lt;/a&gt; assertions (try the BDD style, where it's &lt;code&gt;expect(foo).to.equal(bar)&lt;/code&gt; rather than &lt;code&gt;assert.equal(foo, bar)&lt;/code&gt;); or&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt; and its built-in &lt;a href="https://jestjs.io/docs/en/expect"&gt;assertions&lt;/a&gt; (where it's &lt;code&gt;expect(foo).toBe(bar)&lt;/code&gt;) - try using &lt;a href="https://jestjs.io/docs/en/api#testeachtablename-fn-timeout"&gt;&lt;code&gt;it.each&lt;/code&gt;&lt;/a&gt; for the parameterised tests.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</content><category term="development"></category><category term="javascript"></category><category term="tdd"></category><category term="xp"></category></entry><entry><title>JS TDD Vite</title><link href="https://blog.jonrshar.pe/2023/Dec/17/js-tdd-vite.html" rel="alternate"></link><published>2023-12-17T11:30:00+00:00</published><updated>2024-01-04T16:15:00+00:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2023-12-17:/2023/Dec/17/js-tdd-vite.html</id><summary type="html">&lt;p&gt;Test-driven JavaScript development done right - supplement A&lt;/p&gt;</summary><content type="html">&lt;p&gt;Since I wrote some of the earlier parts of this series (see &lt;a href="https://blog.jonrshar.pe/2020/Nov/22/js-tdd-e2e.html"&gt;part 2&lt;/a&gt; and
&lt;a href="https://blog.jonrshar.pe/2021/Apr/10/js-tdd-api.html"&gt;part 3&lt;/a&gt;), &lt;a href="https://create-react-app.dev/"&gt;Create React App&lt;/a&gt; has become a little stale. As of right
now, a brand new app shows multiple known vulnerabilities on installation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vulnerabilities&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;moderate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kr"&gt;To&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;including&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;breaking&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;audit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="kr"&gt;for&lt;/span&gt;&lt;span class="n"&gt;ce&lt;/span&gt;

&lt;span class="kr"&gt;Run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;audit&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(this &lt;a href="https://github.com/facebook/create-react-app/issues/11174"&gt;isn't necessarily the problem&lt;/a&gt; it might appear, but
likely wouldn't happen at all if the dependencies were kept up-to-date) and a
warning of imminent breakage on build (spelling error in original):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;One&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;babel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;preset&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;react&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;importing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;
&lt;span class="ss"&gt;&amp;quot;@babel/plugin-proposal-private-property-in-object&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;without&lt;/span&gt;
&lt;span class="n"&gt;declaring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;its&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;currently&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;working&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;because&lt;/span&gt;
&lt;span class="ss"&gt;&amp;quot;@babel/plugin-proposal-private-property-in-object&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;already&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;
&lt;span class="n"&gt;node_modules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unrelated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;may&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;babel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;preset&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;react&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;create&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;react&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;which&lt;/span&gt;
&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;maintianed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;anymore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;It&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unlikely&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;will&lt;/span&gt;
&lt;span class="n"&gt;ever&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;@babel/plugin-proposal-private-property-in-object&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;
&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;devDependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;work&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;around&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;away&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(this is trivially fixable in your generated app, but has been known about
for a while and still hasn't been fixed in CRA itself). The last available
release, v5.0.1, dates back to April 12th, 2022 (~18 months ago and counting).
The &lt;a href="https://react.dev/"&gt;new React docs&lt;/a&gt; don't even mention CRA. In this context, it
might be time to think about moving away from CRA for new projects.&lt;/p&gt;
&lt;p&gt;Probably the closest thing to a drop-in equivalent of CRA right now, in terms
of offering an opinionated client-side app setup, is &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt;. So I thought I'd
provide a quick update to show how to get a React app ready for TDD on Vite.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; that if a pure client-side/"single page" app doesn't meet your needs,
there are some recommendations for more complex projects in the official React
docs &lt;a href="https://react.dev/learn/start-a-new-react-project"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="part-1"&gt;Scaffolding the app [1/5]&lt;/h2&gt;
&lt;p&gt;Create a new npm package with the Vite React structure using the following
command (you can use the &lt;code&gt;react&lt;/code&gt; or &lt;code&gt;react-swc&lt;/code&gt; templates - there are also
equivalents with TypeScript pre-configured if you like):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;vite@latest&lt;span class="w"&gt; &lt;/span&gt;test-driven-vite&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;--template&lt;span class="w"&gt; &lt;/span&gt;react
Need&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;following&lt;span class="w"&gt; &lt;/span&gt;packages:
create-vite@5.1.0
Ok&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;proceed?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;y&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;y

Scaffolding&lt;span class="w"&gt; &lt;/span&gt;project&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/test-driven-vite...

Done.&lt;span class="w"&gt; &lt;/span&gt;Now&lt;span class="w"&gt; &lt;/span&gt;run:

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;test-driven-vite
&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Follow the instructions it gave you:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;test-driven-vite
$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install

added&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;270&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages,&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;audited&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;271&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;35s

&lt;span class="m"&gt;97&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;looking&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;funding
&lt;span class="w"&gt;  &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;fund&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details

found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vulnerabilities
$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;dev

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;test-driven-vite@0.0.0&lt;span class="w"&gt; &lt;/span&gt;dev
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;vite


&lt;span class="w"&gt;  &lt;/span&gt;VITE&lt;span class="w"&gt; &lt;/span&gt;v5.0.10&lt;span class="w"&gt;  &lt;/span&gt;ready&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5289&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms

&lt;span class="w"&gt;  &lt;/span&gt;➜&lt;span class="w"&gt;  &lt;/span&gt;Local:&lt;span class="w"&gt;   &lt;/span&gt;http://localhost:5173/
&lt;span class="w"&gt;  &lt;/span&gt;➜&lt;span class="w"&gt;  &lt;/span&gt;Network:&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;--host&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;expose
&lt;span class="w"&gt;  &lt;/span&gt;➜&lt;span class="w"&gt;  &lt;/span&gt;press&lt;span class="w"&gt; &lt;/span&gt;h&lt;span class="w"&gt; &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;enter&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;show&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is roughly equivalent to scaffolding a new CRA app and running &lt;code&gt;npm
start&lt;/code&gt;; you should be able to visit the URL and see a basic app page. However,
there are a couple of things CRA did for us that &lt;code&gt;create-vite&lt;/code&gt; doesn't, so
we'll need a few extra steps before we can start test-driving any real
functionality.&lt;/p&gt;
&lt;h2 id="part-2"&gt;Creating a git repo [2/5]&lt;/h2&gt;
&lt;p&gt;Although the template does include a &lt;code&gt;.gitignore&lt;/code&gt; file, a git repository is
not created by default. If you try checking the status, you can see the
directory doesn't contain one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
fatal:&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;repository&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;any&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;parent&lt;span class="w"&gt; &lt;/span&gt;directories&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;.git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So let's create a fresh git repo, as we did back in &lt;a href="https://blog.jonrshar.pe/2020/Aug/31/js-tdd-ftw.html"&gt;part 1&lt;/a&gt;, then commit the
files &lt;code&gt;create-vite&lt;/code&gt; added for us:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;init
Reinitialized&lt;span class="w"&gt; &lt;/span&gt;existing&lt;span class="w"&gt; &lt;/span&gt;Git&lt;span class="w"&gt; &lt;/span&gt;repository&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/test-driven-vite/.git/
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--allow-empty&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Initial commit&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;root-commit&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cf3ac9f&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Initial&lt;span class="w"&gt; &lt;/span&gt;commit
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Create Vite app&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;f9ab178&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Create&lt;span class="w"&gt; &lt;/span&gt;Vite&lt;span class="w"&gt; &lt;/span&gt;app
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4264&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;.eslintrc.cjs
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# ... other files created&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vite.config.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now all of our changes are safely under version control.&lt;/p&gt;
&lt;h2 id="part-3"&gt;Setting up testing [3/5]&lt;/h2&gt;
&lt;p&gt;Another thing CRA included by default was a test, run with &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt; and using
&lt;a href="https://testing-library.com/docs/react-testing-library/intro"&gt;React Testing Library&lt;/a&gt; to render and select elements. However, we can see
that a new Vite app includes no test script at all:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;Missing&lt;span class="w"&gt; &lt;/span&gt;script:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;To&lt;span class="w"&gt; &lt;/span&gt;see&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;scripts,&lt;span class="w"&gt; &lt;/span&gt;run:
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt;   &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run

npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;A&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;log&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;can&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;path/to/something.log
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As an alternative to Jest, there's &lt;a href="https://vitest.dev/"&gt;Vitest&lt;/a&gt;. This test runner uses the same
build tooling as Vite, and has API compatibility with Jest (so everything you
learned about &lt;code&gt;it&lt;/code&gt;, &lt;code&gt;expect&lt;/code&gt;, etc. still applies).&lt;/p&gt;
&lt;p&gt;So let's install this, as well as JSDOM (which allows the components to be
rendered outside of a real browser environment - this was installed as part
of &lt;code&gt;jest-environment-jsdom&lt;/code&gt; by CRA) and the same Testing Library utilities
we've used previously.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--save-dev&lt;span class="w"&gt; &lt;/span&gt;@testing-library/&lt;span class="o"&gt;{&lt;/span&gt;jest-dom,react,user-event&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jsdom&lt;span class="w"&gt; &lt;/span&gt;vitest

added&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;129&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages,&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;audited&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1s

&lt;span class="m"&gt;124&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;looking&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;funding
&lt;span class="w"&gt;  &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;fund&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details

found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vulnerabilities
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We need a little bit of additional configuration in &lt;code&gt;vite.config.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;export default defineConfig({
&lt;span class="w"&gt; &lt;/span&gt;  plugins: [react()],
&lt;span class="gi"&gt;+  test: {&lt;/span&gt;
&lt;span class="gi"&gt;+    environment: &amp;#39;jsdom&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+    globals: true,&lt;/span&gt;
&lt;span class="gi"&gt;+    setupFiles: [&lt;/span&gt;
&lt;span class="gi"&gt;+      &amp;#39;@testing-library/jest-dom&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+    ],&lt;/span&gt;
&lt;span class="gi"&gt;+  },&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use the JSDOM test environment, to allow browser-based code to work;&lt;/li&gt;
&lt;li&gt;Inject some global functions (e.g. &lt;code&gt;describe&lt;/code&gt; and &lt;code&gt;it&lt;/code&gt;) into the tests, as
   Jest does; and&lt;/li&gt;
&lt;li&gt;Load Testing Library's &lt;a href="https://testing-library.com/docs/ecosystem-jest-dom/"&gt;Jest-DOM&lt;/a&gt; selectors (like &lt;code&gt;.toHaveAttribute&lt;/code&gt;), so we
   can make assertions on the rendered elements.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; instead of using JSDOM (or &lt;a href="https://www.npmjs.com/package/happy-dom"&gt;Happy DOM&lt;/a&gt;, which
Vitest also supports) to provide a mock browser environment, you could try
Vitest's &lt;a href="https://vitest.dev/guide/browser.html"&gt;experimental browser mode&lt;/a&gt; to run the tests
in an actual browser. Here we'll stick with JSDOM for consistency with what
we had in CRA.&lt;/p&gt;
&lt;p&gt;Finally, let's tell npm we want to use Vitest to run our tests:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;pkg&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scripts.test&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;vitest&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Like Jest, Vitest will fail if you try to run it when there are no actual tests:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;test-driven-vite@0.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;vitest


&lt;span class="w"&gt; &lt;/span&gt;DEV&lt;span class="w"&gt;  &lt;/span&gt;v1.0.4&lt;span class="w"&gt; &lt;/span&gt;path/to/test-driven-vite

include:&lt;span class="w"&gt; &lt;/span&gt;**/*.&lt;span class="o"&gt;{&lt;/span&gt;test,spec&lt;span class="o"&gt;}&lt;/span&gt;.?&lt;span class="o"&gt;(&lt;/span&gt;c&lt;span class="p"&gt;|&lt;/span&gt;m&lt;span class="o"&gt;)[&lt;/span&gt;jt&lt;span class="o"&gt;]&lt;/span&gt;s?&lt;span class="o"&gt;(&lt;/span&gt;x&lt;span class="o"&gt;)&lt;/span&gt;
exclude:&lt;span class="w"&gt;  &lt;/span&gt;**/node_modules/**,&lt;span class="w"&gt; &lt;/span&gt;**/dist/**,&lt;span class="w"&gt; &lt;/span&gt;**/cypress/**,&lt;span class="w"&gt; &lt;/span&gt;**/.&lt;span class="o"&gt;{&lt;/span&gt;idea,git,cache,output,temp&lt;span class="o"&gt;}&lt;/span&gt;/**,&lt;span class="w"&gt; &lt;/span&gt;**/&lt;span class="o"&gt;{&lt;/span&gt;karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier&lt;span class="o"&gt;}&lt;/span&gt;.config.*
watch&lt;span class="w"&gt; &lt;/span&gt;exclude:&lt;span class="w"&gt;  &lt;/span&gt;**/node_modules/**,&lt;span class="w"&gt; &lt;/span&gt;**/dist/**

No&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;found,&lt;span class="w"&gt; &lt;/span&gt;exiting&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;code&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="part-4"&gt;Writing a test [4/5]&lt;/h2&gt;
&lt;p&gt;So let's create one, in &lt;code&gt;src/App.spec.jsx&lt;/code&gt;. Pick some aspect of the page
that gets rendered (in this case I've chosen the main heading that's shown) and
write a simple test for it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@testing-library/react&amp;#39;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./App.jsx&amp;#39;&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;App&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;renders a top-level heading&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;heading&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Vite + React&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can see, this looks identical to the sort of thing we had in Jest.
When we run it, it should pass:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;test-driven-vite@0.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;vitest


&lt;span class="w"&gt; &lt;/span&gt;DEV&lt;span class="w"&gt;  &lt;/span&gt;v1.0.4&lt;span class="w"&gt; &lt;/span&gt;path/to/test-driven-vite

&lt;span class="w"&gt; &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;src/App.spec.jsx&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;App&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;renders&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;top-level&lt;span class="w"&gt; &lt;/span&gt;heading

&lt;span class="w"&gt; &lt;/span&gt;Test&lt;span class="w"&gt; &lt;/span&gt;Files&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;Tests&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;Start&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;:09:12
&lt;span class="w"&gt;   &lt;/span&gt;Duration&lt;span class="w"&gt;  &lt;/span&gt;664ms&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;transform&lt;span class="w"&gt; &lt;/span&gt;30ms,&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;89ms,&lt;span class="w"&gt; &lt;/span&gt;collect&lt;span class="w"&gt; &lt;/span&gt;85ms,&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;41ms,&lt;span class="w"&gt; &lt;/span&gt;environment&lt;span class="w"&gt; &lt;/span&gt;277ms,&lt;span class="w"&gt; &lt;/span&gt;prepare&lt;span class="w"&gt; &lt;/span&gt;63ms&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="w"&gt; &lt;/span&gt;PASS&lt;span class="w"&gt;  &lt;/span&gt;Waiting&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changes...
&lt;span class="w"&gt;       &lt;/span&gt;press&lt;span class="w"&gt; &lt;/span&gt;h&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;show&lt;span class="w"&gt; &lt;/span&gt;help,&lt;span class="w"&gt; &lt;/span&gt;press&lt;span class="w"&gt; &lt;/span&gt;q&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;quit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; that like the default CRA Jest setup, Vitest enters a watch mode by
default. To run the tests once then stop, use &lt;code&gt;npm test -- --run&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Quit the test runner when you're satisfied everything is working, then commit
the changes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;package-lock.json
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;package.json
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/App.spec.jsx
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;vite.config.js

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Add a simple test&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;2431a65&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Add&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;simple&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1619&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;51&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/App.spec.jsx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="part-5"&gt;Exercises [5/5]&lt;/h2&gt;
&lt;p&gt;This was just a supplement, so the exercise is pretty simple: redo an earlier
exercise in the series, using Vite/Vitest instead of CRA/Jest.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; that you can set up Cypress exactly as you did before - the
end-to-end tests don't care what (if any) library or framework you're using
to create the page. If you follow the guide from &lt;a href="https://blog.jonrshar.pe/2021/Apr/10/js-tdd-api.html"&gt;part 3&lt;/a&gt; on creating the
&lt;code&gt;e2e:ci&lt;/code&gt; &lt;em&gt;"automatic E2E"&lt;/em&gt;, you don't need to install &lt;code&gt;serve&lt;/code&gt; to test the
app in production mode; &lt;code&gt;vite preview&lt;/code&gt; already does this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{
  // ...
  &amp;quot;scripts&amp;quot;: {
    // ...
    &amp;quot;e2e:ci&amp;quot;: &amp;quot;concurrently --kill-others --success first \&amp;quot;npm:e2e:ci:*\&amp;quot;&amp;quot;,
    &amp;quot;pree2e:ci:app&amp;quot;: &amp;quot;npm run build&amp;quot;,
    &amp;quot;e2e:ci:app&amp;quot;: &amp;quot;npm run preview&amp;quot;,
    &amp;quot;pree2e:ci:run&amp;quot;: &amp;quot;wait-on --log --timeout 60000 http-get://localhost:4173&amp;quot;,
    &amp;quot;e2e:ci:run&amp;quot;: &amp;quot;cross-env CYPRESS_BASE_URL=http://localhost:4173 npm run e2e&amp;quot;,
    // ...
  },
  // ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="bonus"&gt;To global, or not to global? [Bonus]&lt;/h2&gt;
&lt;p&gt;By default, Vitest does &lt;strong&gt;not&lt;/strong&gt; inject anything into the global scope. To keep
things as similar to Jest as possible, we've overridden this with
&lt;code&gt;globals: true&lt;/code&gt; above. Alternatively you could choose the more explicit option,
and omit &lt;code&gt;globals: true&lt;/code&gt; (or explicitly set &lt;code&gt;globals: false&lt;/code&gt;) in the
configuration. But if you do that, you'll need to make some other adjustments:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Firstly, and most obviously, every test file will have to explicitly
   import the functions it needs for defining suites, tests and expectations:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;vitest&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Secondly, the default entrypoint for &lt;code&gt;@testing-library/jest-dom&lt;/code&gt; that we
    used in &lt;code&gt;setupFiles&lt;/code&gt; assumes that &lt;code&gt;expect&lt;/code&gt; will be provided globally. Now
    that it won't be, we have to switch to the Vitest-specific entrypoint
    &lt;code&gt;@testing-library/jest-dom/vitest&lt;/code&gt;, which includes an explicit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;vitest&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;before extending &lt;code&gt;expect&lt;/code&gt; with its own matchers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, React Testing Library's &lt;a href="https://testing-library.com/docs/react-testing-library/api#cleanup"&gt;automatic application of
    &lt;code&gt;cleanup&lt;/code&gt;&lt;/a&gt; only occurs if there's a globally-provided
    &lt;code&gt;afterEach&lt;/code&gt; function for it to hook into. This is explicitly called out in
    the &lt;a href="https://vitest.dev/guide/migration.html#globals-as-a-default"&gt;Vitest migration guide&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you decide to keep globals disabled, be aware that common libraries
like &lt;code&gt;testing-library&lt;/code&gt; will not run auto DOM cleanup.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Without this, each test is adding more and more elements into the render
result, which means your tests can interfere with each other (most likely
with error messages about matching more than one element when only one is
expected, but even worse a test could incorrectly &lt;em&gt;pass&lt;/em&gt; due to something
that was rendered by a previous one still hanging around).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We can deal with 2 and 3 simultaneously by changing the configuration to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;  plugins: [react()],
&lt;span class="w"&gt; &lt;/span&gt;  test: {
&lt;span class="w"&gt; &lt;/span&gt;    environment: &amp;#39;jsdom&amp;#39;,
&lt;span class="gd"&gt;-    globals: true,&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    setupFiles: [
&lt;span class="gd"&gt;-      &amp;#39;@testing-library/jest-dom&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+      &amp;#39;./src/setupTests.js&amp;#39;,&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    ],
&lt;span class="w"&gt; &lt;/span&gt;  },
&lt;span class="w"&gt; &lt;/span&gt;})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and creating the corresponding &lt;code&gt;src/setupTests.js&lt;/code&gt; file containing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@testing-library/jest-dom/vitest&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cleanup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@testing-library/react&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;afterEach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;vitest&amp;#39;&lt;/span&gt;

&lt;span class="nx"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="development"></category><category term="javascript"></category><category term="tdd"></category><category term="xp"></category></entry><entry><title>JS TDD Ohm</title><link href="https://blog.jonrshar.pe/2023/May/23/js-tdd-ohm.html" rel="alternate"></link><published>2023-05-23T14:00:00+01:00</published><updated>2024-08-17T11:30:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2023-05-23:/2023/May/23/js-tdd-ohm.html</id><summary type="html">&lt;p&gt;Test-driven JavaScript development done right - part 4&lt;/p&gt;</summary><content type="html">&lt;p&gt;Welcome to part 4 of this ongoing series on test-driven development (TDD) in JavaScript. So far we've covered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.jonrshar.pe/2020/Aug/31/js-tdd-ftw.html"&gt;Part 1&lt;/a&gt; - the basic principles of test-driven development, showing unit testing using Jest;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.jonrshar.pe/2020/Nov/22/js-tdd-e2e.html"&gt;Part 2&lt;/a&gt; - expanding to higher level testing, using Cypress for E2E and Jest for integration (and unit) testing of a simple React app with Testing Library; and&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.jonrshar.pe/2021/Apr/10/js-tdd-api.html"&gt;Part 3&lt;/a&gt; - introducing some more ideas about isolating your code for testing using test doubles.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you haven't already been through those I'd suggest revisiting at least the first one, as it introduces some terminology and ideas I'll use here.&lt;/p&gt;
&lt;p&gt;This time we're going to dive into test-driving HTTP APIs and talk a bit more about how we can use testing to support us in designing the code we're working on.&lt;/p&gt;
&lt;h3&gt;Requirements&lt;/h3&gt;
&lt;p&gt;The prerequisites here are the same as the previous articles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;*nix command line: already provided on macOS and Linux; if you're using Windows try WSL or Git BASH;&lt;/li&gt;
&lt;li&gt;Node (16+ recommended, Jest 29 is only &lt;a href="https://jestjs.io/docs/upgrading-to-jest29#compatibility"&gt;compatible with&lt;/a&gt; recent LTS versions; run &lt;code&gt;node -v&lt;/code&gt; to check) and NPM; and&lt;/li&gt;
&lt;li&gt;Familiarity with ES6 JavaScript syntax.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition, given the domain for this post, you'll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Familiarity with HTTP requests and responses; and&lt;/li&gt;
&lt;li&gt;Familiarity with &lt;a href="https://expressjs.com/"&gt;Express&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Again please carefully &lt;em&gt;read everything&lt;/em&gt;, and for newer developers I'd recommend &lt;em&gt;typing the code&lt;/em&gt; rather than copy-pasting.&lt;/p&gt;
&lt;h2&gt;Setting the scene [1/9]&lt;/h2&gt;
&lt;p&gt;We're going to be tackling a more realistic case than rock, paper, scissors this time. Our customer, &lt;strong&gt;JonFX&lt;/strong&gt;, sells guitar pedal kits that you construct yourself at home. These kits contain set of instructions and a bunch of electrical components, including resistors: &lt;/p&gt;
&lt;p&gt;&lt;img alt="Picture of some resistors" src="https://blog.jonrshar.pe/images/Electronic-Axial-Lead-Resistors-Array.png"&gt;&lt;/p&gt;
&lt;p&gt;There are three representations of resistance (measured in Ohms, Ω) in use within this ecosystem. For example, given a 22,000Ω resistor, it can be represented as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A number, &lt;code&gt;22_000&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;A shorthand string, &lt;code&gt;"22K"&lt;/code&gt;; or&lt;/li&gt;
&lt;li&gt;A set of bands on the physical component, e.g. &lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: orange; background-color: black"&gt;&amp;nbsp;orange&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our customer has noted that people sometimes have difficulty converting between these representations, and asked us to build something to help solve the problem.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;How do we prioritise which representations we should focus on to start with? We want to deliver the most valuable thing first, so let's do some analysis. There are three &lt;em&gt;personas&lt;/em&gt; who work with these representations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Debbie the designer: Debbie designs the circuits, and generally works with the &lt;em&gt;number&lt;/em&gt; representation. Once a design is complete the values are recorded in a manifest using the &lt;em&gt;shorthand&lt;/em&gt; notation;&lt;/li&gt;
&lt;li&gt;Colin the customer: Colin wants to buy and build one of the kits, which will include the manifest and the components with their &lt;em&gt;bands&lt;/em&gt;; and&lt;/li&gt;
&lt;li&gt;Parul the packer: When Colin orders a kit, Parul is responsible for selecting the components based on the manifest, boxing them up and shipping them out.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Parul and Debbie both work with resistors and other electrical components on a very regular basis, so they probably don't need reminding what the bands mean, and if not there are various non-software interventions we could use to make their lives easier (for example, the boxes Parul is selecting components from could have a picture of the relevant bands and the shorthand printed in large letters to aid selection and refilling). But it might be a while since Colin built his last kit (or he may even be a first-time customer), so that's the persona most likely to need help and therefore the highest value software would focus on the conversion between bands and shorthand, especially when you consider that the company will have far more Colins (thousands) than Paruls (ten) or Debbies (one).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Let's capture that as a &lt;em&gt;user story&lt;/em&gt; that we can refer back to if we need reminding what we're working towards:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;As a&lt;/strong&gt; customer&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I want&lt;/strong&gt; to convert a set of bands to a shorthand string&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So that&lt;/strong&gt; I can match a given resistor to the diagram&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For this exercise, we're going to be building the backend for a web UI; an acceptance criterion based on the above examples might be:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Given&lt;/strong&gt; an input of the bands red, red, orange&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When&lt;/strong&gt; the client makes a request&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then&lt;/strong&gt; the response contains the shorthand &lt;code&gt;"22K"&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: for the sake of simplicity we will be working on an implementation that can convert values from 10Ω (or 100Ω for three value bands) up to but not including 1,000,000,000Ω.&lt;/p&gt;
&lt;h2&gt;Welcome to the resistance [2/9]&lt;/h2&gt;
&lt;p&gt;As shown above, physical resistors have coloured bands which indicate their resistance. The "rules of resistors" that we'll be following are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A resistor must have two or three &lt;em&gt;value&lt;/em&gt; bands, unless it's a 0Ω resistor (which must have only a single black value band);&lt;/li&gt;
&lt;li&gt;The first value band must not be black, unless it's a 0Ω resistor; and&lt;/li&gt;
&lt;li&gt;A resistor must have a single &lt;em&gt;multiplier&lt;/em&gt; band.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The band colours indicate numbers via the following mapping:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;6&lt;/th&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;th&gt;9&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: black"&gt;black&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: brown"&gt;brown&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: orange; background-color: black"&gt;&amp;nbsp;orange&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: yellow; background-color: black"&gt;&amp;nbsp;yellow&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: green"&gt;green&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: blue"&gt;blue&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: purple"&gt;violet&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: dimgrey"&gt;grey&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: white; background-color: black"&gt;&amp;nbsp;white&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The numerical resistance is determined by taking the two or three value bands as the first two or three digits, then adding the number of zeros specified by the multiplier band to the end, e.g.:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Value: &lt;strong&gt;&lt;span style="color: blue"&gt;blue&lt;/span&gt;&lt;/strong&gt; - 6&lt;/li&gt;
&lt;li&gt;Value: &lt;strong&gt;&lt;span style="color: dimgrey"&gt;grey&lt;/span&gt;&lt;/strong&gt; - 8&lt;/li&gt;
&lt;li&gt;Multiplier: &lt;strong&gt;&lt;span style="color: green"&gt;green&lt;/span&gt;&lt;/strong&gt; - 5&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;becomes 6,800,000Ω (6 then 8 then 5 zeros). You could also calculate this as &lt;code&gt;((6 * 10) + 8) * (10 ** 5)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The shorthand form is created by replacing the left-most comma with M (for "mega", meaning a factor of one million) and dropping all trailing zeros; in this case &lt;code&gt;"6M8"&lt;/code&gt;. For values between 1,000Ω and 999,999Ω the comma is replaced with K (for "kilo", meaning a factor of one thousand) instead, hence the 22,000Ω above becomes &lt;code&gt;"22K"&lt;/code&gt;. For values less than 1,000Ω the decimal point is replaced with R, so e.g. 150Ω (bands &lt;strong&gt;&lt;span style="color: brown"&gt;brown&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: green"&gt;green&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: brown"&gt;brown&lt;/span&gt;&lt;/strong&gt;) would be represented as &lt;code&gt;"150R"&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here are a few more examples, or for more details you can read about this &lt;a href="https://en.wikipedia.org/wiki/Electronic_color_code"&gt;electronic colour code&lt;/a&gt; on Wikipedia:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Numeric (Ω)&lt;/th&gt;
&lt;th&gt;Shorthand&lt;/th&gt;
&lt;th&gt;Bands&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"22R"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: black"&gt;black&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12,700&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"12K7"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: brown"&gt;brown&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: purple"&gt;violet&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;330,000&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"330K"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: orange; background-color: black"&gt;&amp;nbsp;orange&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: orange; background-color: black"&gt;&amp;nbsp;orange&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: black"&gt;black&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: orange; background-color: black"&gt;&amp;nbsp;orange&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8,200,000&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"8M2"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: dimgrey"&gt;grey&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: green"&gt;green&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;How can we represent this at the API level? There are a few options, but for the purposes of working through this exercise let's say:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The request method will be &lt;code&gt;GET&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;The request path will be &lt;code&gt;/resistance&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;The bands will be provided as a query parameter named &lt;code&gt;bands&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;The response status code on success will be &lt;code&gt;200&lt;/code&gt; ("OK"); and&lt;/li&gt;
&lt;li&gt;The response body on success will be the shorthand representation as plain text.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using &lt;a href="https://en.wikipedia.org/wiki/CURL"&gt;cURL&lt;/a&gt;, this might look like (assuming an environment variable &lt;code&gt;URL&lt;/code&gt; has been set pointing to our API server):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="s2"&gt;/resistance?bands=brown&amp;amp;bands=red&amp;amp;bands=violet&amp;amp;bands=red&amp;quot;&lt;/span&gt;
12K7
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;None more black [3/9]&lt;/h2&gt;
&lt;p&gt;Let's get started by creating a new NPM package to hold our API:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;resistance
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$_&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;init
Reinitialized&lt;span class="w"&gt; &lt;/span&gt;existing&lt;span class="w"&gt; &lt;/span&gt;Git&lt;span class="w"&gt; &lt;/span&gt;repository&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance/.git/
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--allow-empty&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Initial commit&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;root-commit&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;7c30cd9&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Initial&lt;span class="w"&gt; &lt;/span&gt;commit
$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;--yes
Wrote&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;path/to/resistance/package.json:

&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;resistance&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;version&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1.0.0&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;main&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;index.js&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scripts&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;keywords&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Create NPM package&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;6c30da5&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Create&lt;span class="w"&gt; &lt;/span&gt;NPM&lt;span class="w"&gt; &lt;/span&gt;package
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;package.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We'll use &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt; again for testing, and add &lt;a href="https://www.npmjs.com/package/supertest"&gt;Supertest&lt;/a&gt; as an adapter between the test runner and the API, to make it easier to make requests and assert on the responses (I've explained why I think this is better than just using e.g. Axios to make the requests &lt;a href="https://stackoverflow.com/a/62992056/3001761"&gt;here&lt;/a&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--save-dev&lt;span class="w"&gt; &lt;/span&gt;jest&lt;span class="w"&gt; &lt;/span&gt;supertest

added&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;305&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages,&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;audited&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;306&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;7s

&lt;span class="m"&gt;38&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;looking&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;funding
&lt;span class="w"&gt;  &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;fund&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details

found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vulnerabilities
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;node_modules/&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;.gitignore
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;staged&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;commit:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git add &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;what&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;discard&lt;span class="w"&gt; &lt;/span&gt;changes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;working&lt;span class="w"&gt; &lt;/span&gt;directory&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;package.json

Untracked&lt;span class="w"&gt; &lt;/span&gt;files:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git add &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;include&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;what&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;.gitignore
&lt;span class="w"&gt;        &lt;/span&gt;package-lock.json

no&lt;span class="w"&gt; &lt;/span&gt;changes&lt;span class="w"&gt; &lt;/span&gt;added&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git add&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;and/or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git commit -a&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Install test dependencies&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;56d2180&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dependencies
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6553&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletion&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;.gitignore
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;package-lock.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Create &lt;code&gt;app.test.js&lt;/code&gt; and write a test. Let's start with the &lt;strong&gt;simplest possible case&lt;/strong&gt;; the 0Ω resistor, a single black band:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;supertest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;resistance API&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;returns 0R for a single black band&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bands&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0R&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here you can see how Supertest's API lets us specify the request to make (method, path and query parameters) and assert on the response (status code and body). Set up Jest as the test runner using &lt;a href="https://docs.npmjs.com/cli/v9/commands/npm-pkg"&gt;NPM's &lt;code&gt;pkg&lt;/code&gt; command&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;pkg&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scripts.test&lt;span class="o"&gt;=&lt;/span&gt;jest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;What will happen when you run the test? &lt;strong&gt;Call the shot&lt;/strong&gt;, then use &lt;code&gt;npm test&lt;/code&gt; to run it.&lt;/p&gt;
&lt;hr&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;resistance@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest

&lt;span class="w"&gt; &lt;/span&gt;FAIL&lt;span class="w"&gt;  &lt;/span&gt;./app.test.js
&lt;span class="w"&gt;  &lt;/span&gt;resistance&lt;span class="w"&gt; &lt;/span&gt;API
&lt;span class="w"&gt;    &lt;/span&gt;✕&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;0R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;black&lt;span class="w"&gt; &lt;/span&gt;band&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;resistance&lt;span class="w"&gt; &lt;/span&gt;API&lt;span class="w"&gt; &lt;/span&gt;›&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;0R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;black&lt;span class="w"&gt; &lt;/span&gt;band

&lt;span class="w"&gt;    &lt;/span&gt;ReferenceError:&lt;span class="w"&gt; &lt;/span&gt;app&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;defined

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;describe&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;resistance API&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;it&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;returns 0R for a single black band&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;request&lt;span class="o"&gt;(&lt;/span&gt;app&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.query&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bands:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.expect&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0R&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.app&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;app.test.js:5:20&lt;span class="o"&gt;)&lt;/span&gt;

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.224&lt;span class="w"&gt; &lt;/span&gt;s,&lt;span class="w"&gt; &lt;/span&gt;estimated&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Hopefully you predicted that: &lt;code&gt;app&lt;/code&gt; wasn't defined, the test crashed before even getting the chance to fail. So let's give it an app to test! Start by installing Express:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;express

added&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;53&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages,&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;audited&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;359&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;880ms

&lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;looking&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;funding
&lt;span class="w"&gt;  &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;fund&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details

found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vulnerabilities
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Create a new file &lt;code&gt;app.js&lt;/code&gt;, and set up a basic Express application:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;express&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;then add the import into &lt;code&gt;app.test.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt; const request = require(&amp;quot;supertest&amp;quot;);
&lt;span class="gi"&gt;+ &lt;/span&gt;
&lt;span class="gi"&gt;+ const app = require(&amp;quot;./app&amp;quot;);&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt; describe(&amp;quot;resistance API&amp;quot;, () =&amp;gt; {
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;What will happen when we re-run the test now? &lt;strong&gt;Call the shot&lt;/strong&gt;, then run it again.&lt;/p&gt;
&lt;hr&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;resistance@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest

&lt;span class="w"&gt; &lt;/span&gt;FAIL&lt;span class="w"&gt;  &lt;/span&gt;./app.test.js
&lt;span class="w"&gt;  &lt;/span&gt;resistance&lt;span class="w"&gt; &lt;/span&gt;API
&lt;span class="w"&gt;    &lt;/span&gt;✕&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;0R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;black&lt;span class="w"&gt; &lt;/span&gt;band&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;resistance&lt;span class="w"&gt; &lt;/span&gt;API&lt;span class="w"&gt; &lt;/span&gt;›&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;0R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;black&lt;span class="w"&gt; &lt;/span&gt;band

&lt;span class="w"&gt;    &lt;/span&gt;expected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;OK&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;got&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;404&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Not Found&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.query&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bands:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.expect&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0R&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.expect&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;app.test.js:10:8&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;----
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test._assertStatus&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:252:14&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;node_modules/supertest/lib/test.js:308:13
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test._assertFunction&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:285:13&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:164:23&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Server.localAssert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:120:14&lt;span class="o"&gt;)&lt;/span&gt;

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.232&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's a bit more like it, the test is now &lt;em&gt;failing&lt;/em&gt; (rather than &lt;em&gt;crashing&lt;/em&gt;) and we're getting feedback at the HTTP API level (404 Not Found status code instead of the expected 200 OK). Let's handle that endpoint and move the failure a bit further along; add the code to &lt;code&gt;app.js&lt;/code&gt; to handle the GET request and immediately return 200 OK:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we should see a failure for the body of the response, rather than the status code: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;resistance&lt;span class="w"&gt; &lt;/span&gt;API&lt;span class="w"&gt; &lt;/span&gt;›&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;0R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;black&lt;span class="w"&gt; &lt;/span&gt;band

&lt;span class="w"&gt;    &lt;/span&gt;expected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;0R&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;response&lt;span class="w"&gt; &lt;/span&gt;body,&lt;span class="w"&gt; &lt;/span&gt;got&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;OK&amp;#39;&lt;/span&gt;

&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.query&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bands:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.expect&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0R&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.expect&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;app.test.js:10:8&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If not, you may be handling the wrong path or method; double-check that the code in &lt;code&gt;app.js&lt;/code&gt; matches up with the request defined in &lt;code&gt;app.test.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finish up the first step by updating the handler so that the test passes, then make a commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Implement 0 Ohm resistor&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;51abbe9&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Implement&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Ohm&lt;span class="w"&gt; &lt;/span&gt;resistor
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;964&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;app.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;app.test.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: test-driving the development has already influenced one part of our system's design - wanting to access the application directly means we've exported it rather than immediately calling &lt;code&gt;app.listen&lt;/code&gt; to start it up. If you'd like to try out the server locally (e.g. using cURL or Postman) while you're working on it, create the following &lt;code&gt;server.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./app.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;3000&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`listening on &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;then &lt;code&gt;npm install --save-dev nodemon&lt;/code&gt; and update the scripts in &lt;code&gt;package.json&lt;/code&gt; with the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;   &amp;quot;scripts&amp;quot;: {
&lt;span class="gi"&gt;+     &amp;quot;dev&amp;quot;: &amp;quot;nodemon ./server.js&amp;quot;,&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;     &amp;quot;test&amp;quot;: &amp;quot;jest&amp;quot;
&lt;span class="w"&gt; &lt;/span&gt;   }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now &lt;code&gt;npm run dev&lt;/code&gt; will start the app, and restart whenever you save changes. But you might find that you don't &lt;em&gt;need&lt;/em&gt; to try out the server manually, because all the tests mean you're already confident that it works, and that's worth reflecting on!&lt;/p&gt;
&lt;h2&gt;Unhappy path to design [4/9]&lt;/h2&gt;
&lt;p&gt;At this point you might be tempted to jump straight to an example like the 22kΩ resistor in the introduction, writing something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;returns 22K for red, red, orange&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bands&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;red&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;red&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;orange&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;22K&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But bear in mind that this is an HTTP API. Anyone can make a request to it, and they might not send one that's well-formed. In my case, where it's expecting a request like &lt;code&gt;/resistance?bands=black&lt;/code&gt;, what if there &lt;em&gt;isn't&lt;/em&gt; a query parameter? I've found this &lt;a href="https://www.codetinkerer.com/2015/12/04/choosing-an-http-status-code.html"&gt;status code flowchart&lt;/a&gt; really useful for figuring out a semantically appropriate response; working through that I get down to &lt;code&gt;400 Bad Request&lt;/code&gt;. So let's write &lt;em&gt;that&lt;/em&gt; test:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;returns 400 if query missing&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Bad Request&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Follow the TDD process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Call the shot;&lt;/li&gt;
&lt;li&gt;Run the test;&lt;/li&gt;
&lt;li&gt;Ensure it fails usefully (edit the test and repeat steps 1 and 2 as needed);&lt;/li&gt;
&lt;li&gt;Get it passing; and&lt;/li&gt;
&lt;li&gt;Make a commit.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Remember&lt;/strong&gt;: never rely on your clients to make valid requests. Even if you only intend for the API to be consumed by e.g. a React app you're maintaining, always check that input validation and authentication is applied correctly; it's trivial to make a request &lt;em&gt;without&lt;/em&gt; using the UI.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Next, what if there is a &lt;code&gt;bands&lt;/code&gt; query parameter but its value isn't &lt;code&gt;black&lt;/code&gt;? That's a &lt;em&gt;structurally&lt;/em&gt; valid request, it has the query parameter, but e.g. &lt;code&gt;/resistance?bands=blue&lt;/code&gt; is &lt;em&gt;semantically&lt;/em&gt; invalid; there's no real resistor with a single blue band. From the above flowchart, I get to &lt;code&gt;422 Unprocessable Entity&lt;/code&gt;. So let's write a second test for that.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;returns 422 for a single non-black band&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bands&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;422&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Unprocessable Entity&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The temptation here might be to do something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bands&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;bands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bands&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;422&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0R&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;However, this is mixing up two very important concepts. We have two &lt;em&gt;domains&lt;/em&gt; here, &lt;strong&gt;transport&lt;/strong&gt; (HTTP requests and responses, things like paths, query parameters and status codes) and &lt;strong&gt;business&lt;/strong&gt; (resistors and their resistance values). Splitting this out into those two domains might look like:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request&lt;/th&gt;
&lt;th&gt;Transport&lt;/th&gt;
&lt;th&gt;Business&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /resistance&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;"A request with no &lt;code&gt;bands&lt;/code&gt; query parameter is bad."&lt;/em&gt; -&amp;gt; &lt;code&gt;400&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /resistance?bands=blue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;"An invalid resistor isn't processable."&lt;/em&gt; -&amp;gt; &lt;code&gt;422&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;"A resistor with a single blue band isn't valid."&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Here you can see the split described above - the left-hand side is about HTTP APIs, the right-hand side is about resistors.  While handling a &lt;em&gt;structurally&lt;/em&gt; invalid request can be done entirely at the transport level, handling a &lt;em&gt;semantically&lt;/em&gt; invalid request is a business level question.&lt;/p&gt;
&lt;p&gt;So let's take this opportunity to split out a &lt;em&gt;service&lt;/em&gt; in &lt;code&gt;service.js&lt;/code&gt; to handle the business domain:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resistance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0R&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and use that in the app:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resistance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./service&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bands&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;bands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bands&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is a simple &lt;em&gt;refactor&lt;/em&gt;, the &lt;code&gt;200&lt;/code&gt; and &lt;code&gt;400&lt;/code&gt; tests should still pass, and the &lt;code&gt;422&lt;/code&gt; test should still fail (you can comment it out or &lt;a href="https://jestjs.io/docs/api#testskipname-fn"&gt;skip it&lt;/a&gt; to double-check). It also gives us a new &lt;em&gt;boundary&lt;/em&gt; to test at, we can exercise the service code directly in &lt;code&gt;service.test.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resistance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./service&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;resistance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;returns 0R for a single black band&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0R&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can run these low-level tests on their own by passing the file name as an argument to Jest, &lt;code&gt;npm test -- service&lt;/code&gt;. So how should we handle an invalid band? Again this gives us a chance to do some design, think through how the function should behave by writing the test &lt;em&gt;before&lt;/em&gt; the implementation. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We could return &lt;code&gt;null&lt;/code&gt; for cases where the bands aren't valid, &lt;code&gt;expect(resistance(["red"]).toBeNull()&lt;/code&gt;,  but if &lt;em&gt;all&lt;/em&gt; we get back from the function in the failing case is &lt;code&gt;null&lt;/code&gt; that doesn't tell us much about what the problem was;&lt;/li&gt;
&lt;li&gt;We could return a string describing the problem, but that would make it very difficult for the controller to distinguish between valid and invalid cases to send the appropriate responses;&lt;/li&gt;
&lt;li&gt;We could return an object, &lt;code&gt;expect(resistance(["red"]).toEqual({ error: "..." })&lt;/code&gt;, but that doesn't exactly scream &lt;em&gt;"your input made no sense"&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I would say the right thing to do here is to &lt;strong&gt;throw an error&lt;/strong&gt;, which can have a message explaining what the problem was. Remember that you have to pass a &lt;em&gt;function&lt;/em&gt; when you expect an error to be thrown, to defer the execution of the thing you're testing, otherwise (with e.g. &lt;code&gt;expect(resistance["blue"])).toThrow(...)&lt;/code&gt;) the error is thrown &lt;em&gt;before&lt;/em&gt; &lt;code&gt;expect&lt;/code&gt; gets called:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;throws an error for a single non-black band&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])).&lt;/span&gt;&lt;span class="nx"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Invalid bands: blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: it seems to be a popular pattern to write an exception with a status code for this kind of thing, e.g.:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// custom error:&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CustomError&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ne"&gt;Error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// in the service:&lt;/span&gt;
&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CustomError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;422&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// in the controller/middleware:&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;instanceof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CustomError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I think that this is bad design - the whole point of extracting the service was to &lt;em&gt;isolate&lt;/em&gt; our business logic from details of the transport layer. Imagine we reused the core service with a &lt;em&gt;different&lt;/em&gt; transport layer, e.g. a CLI wrapper to allow usage on the command line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;./resistance.js&lt;span class="w"&gt; &lt;/span&gt;yellow&lt;span class="w"&gt; &lt;/span&gt;violet&lt;span class="w"&gt; &lt;/span&gt;black
4K7
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now what does the &lt;code&gt;status&lt;/code&gt; on the error mean, what is &lt;code&gt;422&lt;/code&gt; in the context of a CLI? We've re-coupled our core domain/business logic back to the transport layer, we might as well have written everything in the controller!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Call the shot, run the test, check the diagnostics:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;●&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resistance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;›&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;throws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;single&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;non-black&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;band&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;expect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;received&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;toThrow&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;expected&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Expected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;substring&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Invalid bands: blue&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Received&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;did&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;throw&lt;/span&gt;

&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nt"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nt"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;it&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;throws an error for a single non-black band&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;expect(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;resistance(&lt;/span&gt;&lt;span class="cp"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;]&lt;/span&gt;&lt;span class="err"&gt;)).toThrow(&amp;quot;Invalid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;blue&lt;/span&gt;&lt;span class="err"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                                        &lt;/span&gt;&lt;span class="err"&gt;^&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;toThrow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;js&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;9&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;40&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Get that test passing at the service level, then run all of the tests to bring the integration tests back in (remember to &lt;strong&gt;call the shot&lt;/strong&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;resistance@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest

FAIL&lt;span class="w"&gt; &lt;/span&gt;./app.test.js
&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;resistance&lt;span class="w"&gt; &lt;/span&gt;›&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;0R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;black&lt;span class="w"&gt; &lt;/span&gt;band

&lt;span class="w"&gt;    &lt;/span&gt;expected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;OK&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;got&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Internal Server Error&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.query&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bands:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.expect&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0R&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;it&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;returns 422 for a single non-black band&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.expect&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;app.test.js:16:8&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;----
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test._assertStatus&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:252:14&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;node_modules/supertest/lib/test.js:308:13
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test._assertFunction&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:285:13&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:164:23&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Server.localAssert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:120:14&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;resistance&lt;span class="w"&gt; &lt;/span&gt;›&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;non-black&lt;span class="w"&gt; &lt;/span&gt;band

&lt;span class="w"&gt;    &lt;/span&gt;expected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Unprocessable Entity&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;got&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Internal Server Error&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.query&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bands:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;23&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.expect&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Unprocessable Entity&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;26&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.expect&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;app.test.js:23:8&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;----
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test._assertStatus&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:252:14&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;node_modules/supertest/lib/test.js:308:13
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test._assertFunction&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:285:13&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:164:23&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Server.localAssert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:120:14&lt;span class="o"&gt;)&lt;/span&gt;

PASS&lt;span class="w"&gt; &lt;/span&gt;./service.test.js

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.26&lt;span class="w"&gt; &lt;/span&gt;s,&lt;span class="w"&gt; &lt;/span&gt;estimated&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's unfortunate; &lt;em&gt;two&lt;/em&gt; of the tests are failing. I was expecting only &lt;em&gt;one&lt;/em&gt; failure, we still handle the single black band case correctly. And even worse we don't see very much information about &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: this is a good motivation for running the code in a known-failing state, as TDD encourages - you get a preview of what errors in production would look like, and it this case it's told us we need better observability (&lt;em&gt;"o11y"&lt;/em&gt;)!&lt;/p&gt;
&lt;p&gt;To help with debugging, add the following Express &lt;a href="https://expressjs.com/en/guide/using-middleware.html"&gt;middleware&lt;/a&gt; to the end of &lt;code&gt;app.js&lt;/code&gt; to ensure we see any unhandled errors in the server logs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headersSent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and re-run the tests (I've trimmed any error tracebacks to exclude external code - they're very long otherwise):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;resistance@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest

&lt;span class="w"&gt; &lt;/span&gt;FAIL&lt;span class="w"&gt;  &lt;/span&gt;./app.test.js
&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;Console

&lt;span class="w"&gt;    &lt;/span&gt;console.error
&lt;span class="w"&gt;      &lt;/span&gt;Error:&lt;span class="w"&gt; &lt;/span&gt;Invalid&lt;span class="w"&gt; &lt;/span&gt;bands:&lt;span class="w"&gt; &lt;/span&gt;black
&lt;span class="w"&gt;          &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.&amp;lt;anonymous&amp;gt;.module.exports.resistance&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/resistance/service.js:5:9&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;resistance&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/resistance/app.js:12:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;...

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;app.use&lt;span class="o"&gt;((&lt;/span&gt;err,&lt;span class="w"&gt; &lt;/span&gt;req,&lt;span class="w"&gt; &lt;/span&gt;res,&lt;span class="w"&gt; &lt;/span&gt;next&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;!res.headersSent&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;console.error&lt;span class="o"&gt;(&lt;/span&gt;err&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;res.sendStatus&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;next&lt;span class="o"&gt;(&lt;/span&gt;err&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;app.js:17:13&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;...

&lt;span class="w"&gt;    &lt;/span&gt;console.error
&lt;span class="w"&gt;      &lt;/span&gt;Error:&lt;span class="w"&gt; &lt;/span&gt;Invalid&lt;span class="w"&gt; &lt;/span&gt;bands:&lt;span class="w"&gt; &lt;/span&gt;blue
&lt;span class="w"&gt;          &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.&amp;lt;anonymous&amp;gt;.module.exports.resistance&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/resistance/service.js:5:9&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;resistance&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/resistance/app.js:12:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;...

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;app.use&lt;span class="o"&gt;((&lt;/span&gt;err,&lt;span class="w"&gt; &lt;/span&gt;req,&lt;span class="w"&gt; &lt;/span&gt;res,&lt;span class="w"&gt; &lt;/span&gt;next&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;!res.headersSent&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;console.error&lt;span class="o"&gt;(&lt;/span&gt;err&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;res.sendStatus&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;next&lt;span class="o"&gt;(&lt;/span&gt;err&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;app.js:17:13&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;...

&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;resistance&lt;span class="w"&gt; &lt;/span&gt;API&lt;span class="w"&gt; &lt;/span&gt;›&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;0R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;black&lt;span class="w"&gt; &lt;/span&gt;band

&lt;span class="w"&gt;    &lt;/span&gt;expected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;OK&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;got&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Internal Server Error&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.query&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bands:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.expect&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0R&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;it&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;returns 400 if query missing&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.expect&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;app.test.js:10:8&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;----
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test._assertStatus&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:252:14&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;node_modules/supertest/lib/test.js:308:13
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test._assertFunction&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:285:13&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:164:23&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Server.localAssert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:120:14&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;resistance&lt;span class="w"&gt; &lt;/span&gt;API&lt;span class="w"&gt; &lt;/span&gt;›&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;non-black&lt;span class="w"&gt; &lt;/span&gt;band

&lt;span class="w"&gt;    &lt;/span&gt;expected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Unprocessable Entity&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;got&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Internal Server Error&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.query&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bands:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;23&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.expect&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Unprocessable Entity&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;26&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.expect&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;app.test.js:23:8&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;----
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test._assertStatus&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:252:14&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;node_modules/supertest/lib/test.js:308:13
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test._assertFunction&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:285:13&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:164:23&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Server.localAssert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:120:14&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;PASS&lt;span class="w"&gt;  &lt;/span&gt;./service.test.js

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.384&lt;span class="w"&gt; &lt;/span&gt;s,&lt;span class="w"&gt; &lt;/span&gt;estimated&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I was expecting &lt;code&gt;Invalid bands: blue&lt;/code&gt;, but &lt;code&gt;Invalid bands: black&lt;/code&gt;? That's the one case we thought we'd handled! Try &lt;em&gt;debugging&lt;/em&gt; to find out what's going on; put a breakpoint on the first line of the controller and then run the tests with a debugger attached (e.g. in Visual Studio Code run &lt;code&gt;npm test&lt;/code&gt; in the &lt;a href="https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_javascript-debug-terminal"&gt;JavaScript Debug Terminal&lt;/a&gt;, in WebStorm debug the test &lt;a href="https://www.jetbrains.com/help/webstorm/running-unit-tests-on-jest.html#ws_jest_run_single_test_from_editor"&gt;from the editor&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: another good motivation for writing tests, it gives you a really easy entrypoint for debugging small sections of your program.&lt;/p&gt;
&lt;p&gt;When you do so you should find out that &lt;code&gt;req.query&lt;/code&gt; is &lt;code&gt;{ bands: "black" }&lt;/code&gt; - &lt;code&gt;bands&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; an array. This happens because of the way Express deserialises query parameters, &lt;code&gt;?foo=bar&lt;/code&gt; becomes &lt;code&gt;{ foo: "bar" }&lt;/code&gt; whereas &lt;code&gt;?foo=bar&amp;amp;foo=baz&lt;/code&gt; gives &lt;code&gt;{ foo: ["bar", "baz"] }&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So where do we fix it? From a design perspective I would say that the interface to our service that our unit tests describe is correct - if we were calling that function from anywhere else (e.g. imagine we also had a CLI tool or a desktop app) we'd be passing in an array of strings. So this is something that the &lt;em&gt;transport&lt;/em&gt; layer for our API should be handing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;   }
&lt;span class="gd"&gt;-   res.send(resistance(bands));&lt;/span&gt;
&lt;span class="gi"&gt;+   res.send(resistance(Array.isArray(bands) ? bands : [bands]));&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt; });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This keeps a very neat split - the transport layer is all about converting between HTTP and our service representation, an array of strings, then the service is purely about resistors and their bands. Call the shot and run the tests again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;resistance@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest

FAIL&lt;span class="w"&gt; &lt;/span&gt;./app.test.js
&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;Console

&lt;span class="w"&gt;    &lt;/span&gt;console.error
&lt;span class="w"&gt;      &lt;/span&gt;Error:&lt;span class="w"&gt; &lt;/span&gt;Invalid&lt;span class="w"&gt; &lt;/span&gt;bands:&lt;span class="w"&gt; &lt;/span&gt;blue
&lt;span class="w"&gt;          &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.&amp;lt;anonymous&amp;gt;.module.exports.resistance&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/resistance/service.js:3:11&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;resistance&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/resistance/app.js:15:12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;...

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;app.use&lt;span class="o"&gt;((&lt;/span&gt;err,&lt;span class="w"&gt; &lt;/span&gt;req,&lt;span class="w"&gt; &lt;/span&gt;res,&lt;span class="w"&gt; &lt;/span&gt;next&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;!req.headersSent&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;console.error&lt;span class="o"&gt;(&lt;/span&gt;err&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;res.sendStatus&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;23&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;next&lt;span class="o"&gt;(&lt;/span&gt;err&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;app.js:20:13&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;...

&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;resistance&lt;span class="w"&gt; &lt;/span&gt;API&lt;span class="w"&gt; &lt;/span&gt;›&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;non-black&lt;span class="w"&gt; &lt;/span&gt;band

&lt;span class="w"&gt;    &lt;/span&gt;expected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Unprocessable Entity&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;got&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Internal Server Error&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/resistance&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.query&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bands:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;23&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;.expect&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Unprocessable Entity&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;26&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.expect&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;app.test.js:23:8&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;----
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test._assertStatus&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:252:14&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;node_modules/supertest/lib/test.js:308:13
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test._assertFunction&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:285:13&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Test.assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:164:23&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Server.localAssert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/supertest/lib/test.js:120:14&lt;span class="o"&gt;)&lt;/span&gt;

PASS&lt;span class="w"&gt; &lt;/span&gt;./service.test.js

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.342&lt;span class="w"&gt; &lt;/span&gt;s,&lt;span class="w"&gt; &lt;/span&gt;estimated&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's much better, we only have one failing test and can see the error at the &lt;em&gt;business&lt;/em&gt; level, so we just need to catch it in &lt;code&gt;app.js&lt;/code&gt; and respond appropriately to the request to get the tests passing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;resistance@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest

PASS&lt;span class="w"&gt; &lt;/span&gt;./app.test.js
PASS&lt;span class="w"&gt; &lt;/span&gt;./service.test.js

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.353&lt;span class="w"&gt; &lt;/span&gt;s,&lt;span class="w"&gt; &lt;/span&gt;estimated&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once you're there, make a commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;staged&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;commit:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git add &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;what&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;discard&lt;span class="w"&gt; &lt;/span&gt;changes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;working&lt;span class="w"&gt; &lt;/span&gt;directory&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;app.js
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;app.test.js

Untracked&lt;span class="w"&gt; &lt;/span&gt;files:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git add &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;include&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;what&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;service.js
&lt;span class="w"&gt;        &lt;/span&gt;service.test.js

no&lt;span class="w"&gt; &lt;/span&gt;changes&lt;span class="w"&gt; &lt;/span&gt;added&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git add&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;and/or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git commit -a&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Handle error cases&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;0e5b121&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Handle&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;cases
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;49&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletion&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;service.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;service.test.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Double trouble [5/9]&lt;/h2&gt;
&lt;p&gt;An obvious next step at this point is to test what happens with &lt;em&gt;two&lt;/em&gt; bands, which is also invalid according to our rules. Let's add a bit more structure to the low-level test cases and add one for two bands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resistance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./service&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;resistance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;one band&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;returns 0R for a single black band&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0R&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;throws an error for a single non-black band&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])).&lt;/span&gt;&lt;span class="nx"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Invalid bands: blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;two bands&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;throws an error&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])).&lt;/span&gt;&lt;span class="nx"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Invalid bands: black,blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It's worth noting that I've chosen to have &lt;code&gt;"black"&lt;/code&gt; as the first of two bands &lt;em&gt;specifically&lt;/em&gt;; this &lt;em&gt;was&lt;/em&gt; a valid first band for a 0Ω resistor, but isn't otherwise. Any two-band "resistor" is invalid, but using this test case rules out the possibility that we &lt;em&gt;only&lt;/em&gt; check whether the first band is black (and not e.g. how many there are).&lt;/p&gt;
&lt;p&gt;Call the shot, run the test. If it fails (it may not, depending on how you've implemented the service so far!) then get it passing. We already know that the API will respond 422 if the service throws an error, so we're done; make a commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Error for two bands&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;48863a8&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Error&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;two&lt;span class="w"&gt; &lt;/span&gt;bands
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Plotting a course [6/9]&lt;/h2&gt;
&lt;p&gt;Now we're in a nice position - we've designed and implemented an API, factored our app into &lt;em&gt;transport&lt;/em&gt; and &lt;em&gt;business&lt;/em&gt; domains, and are testing the integration across three cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No bands - &lt;em&gt;structurally&lt;/em&gt; invalid, service doesn't get called, 400 response;&lt;/li&gt;
&lt;li&gt;One black band - service gets called, 200 response with its return value; and&lt;/li&gt;
&lt;li&gt;One non-black band or two bands - &lt;em&gt;semantically&lt;/em&gt; invalid, service gets called, 422 response on error.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sure, we're only dealing with a single, trivial valid case: a 0Ω resistor, with a single black band (which is basically just a wire in the packaging of a resistor!) Our code isn't going to help our end users much at this stage, but we've set the foundations to be able to confidently and rapidly iterate on the core functionality. And if the user &lt;em&gt;does&lt;/em&gt; have a resistor with a single black band it gives them the correct answer!&lt;/p&gt;
&lt;p&gt;Now, how to approach the more useful cases and actually return some non-zero answers?&lt;/p&gt;
&lt;p&gt;In general, when I'm trying to work my way through a problem like this, I try to think about what the next &lt;em&gt;simplest&lt;/em&gt; step is - not just in the implementation to get the test passing, but in the &lt;em&gt;logic&lt;/em&gt; to write a failing test. &lt;/p&gt;
&lt;p&gt;Let's keep using the 22,000Ω/&lt;code&gt;"22K"&lt;/code&gt; case we started with. Thinking about the three bands we are using, I'd propose that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;second&lt;/strong&gt; band is the simplest to deal with, as it can represent any value 0-9 (&lt;code&gt;"20K"&lt;/code&gt;, &lt;code&gt;"21K"&lt;/code&gt;, ...); then&lt;/li&gt;
&lt;li&gt;The first band is the next simplest, as it can represent 1-9 (&lt;code&gt;"12K"&lt;/code&gt;, &lt;code&gt;"22K"&lt;/code&gt;, ...) but not 0 (throws an error leading to 422 response status); and finally&lt;/li&gt;
&lt;li&gt;The third is the most complex, as both the character and its &lt;em&gt;position&lt;/em&gt; can change (&lt;code&gt;"22R"&lt;/code&gt;, &lt;code&gt;"220R"&lt;/code&gt;, ...).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To keep us on track as we work towards the result, start with an integration-level test for a &lt;em&gt;different&lt;/em&gt; example, one we're not actually going to reach until all three bands are handled. E.g. if the unit-level cases are based around the 22,000Ω example, use the 6,800,000Ω example for the integration-level case. That stops us getting overexcited and shipping once we've handled both value bands but not yet the multiplier. The alternative would be to ensure that cases that aren't yet supported explicitly throw an error, returning a 422 status, which means adding extra tests early on then deleting them as they become irrelevant (this is also an acceptable part of TDD).&lt;/p&gt;
&lt;p&gt;So work through the cases in that order, writing &lt;em&gt;parameterised tests&lt;/em&gt; for each group. By the time you're finished the suite at the service level should look something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;resistance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;one band&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;two bands&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;three bands&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// second band cases&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;throws an error for a leading black band&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;red&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;orange&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Invalid bands: black,red,orange&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// other first band cases&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// third band cases&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Giving test outputs like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;--verbose

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;resistance@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest&lt;span class="w"&gt; &lt;/span&gt;--verbose

&lt;span class="w"&gt; &lt;/span&gt;PASS&lt;span class="w"&gt;  &lt;/span&gt;./app.test.js
&lt;span class="w"&gt;  &lt;/span&gt;resistance&lt;span class="w"&gt; &lt;/span&gt;API
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;0R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;black&lt;span class="w"&gt; &lt;/span&gt;band&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;query&lt;span class="w"&gt; &lt;/span&gt;missing&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;422&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;non-black&lt;span class="w"&gt; &lt;/span&gt;band&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;6K8&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;blue,&lt;span class="w"&gt; &lt;/span&gt;grey,&lt;span class="w"&gt; &lt;/span&gt;red&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;PASS&lt;span class="w"&gt;  &lt;/span&gt;./service.test.js
&lt;span class="w"&gt;  &lt;/span&gt;resistance
&lt;span class="w"&gt;    &lt;/span&gt;one&lt;span class="w"&gt; &lt;/span&gt;band
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;0R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;black&lt;span class="w"&gt; &lt;/span&gt;band&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;throws&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;non-black&lt;span class="w"&gt; &lt;/span&gt;band&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;two&lt;span class="w"&gt; &lt;/span&gt;bands
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;throws&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;three&lt;span class="w"&gt; &lt;/span&gt;bands
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;20K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;black,&lt;span class="w"&gt; &lt;/span&gt;orange
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;21K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;brown,&lt;span class="w"&gt; &lt;/span&gt;orange
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;22K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;orange
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;23K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;orange,&lt;span class="w"&gt; &lt;/span&gt;orange
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;24K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;yellow,&lt;span class="w"&gt; &lt;/span&gt;orange&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;25K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;green,&lt;span class="w"&gt; &lt;/span&gt;orange
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;26K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;blue,&lt;span class="w"&gt; &lt;/span&gt;orange
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;27K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;violet,&lt;span class="w"&gt; &lt;/span&gt;orange
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;28K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;grey,&lt;span class="w"&gt; &lt;/span&gt;orange&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;29K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;white,&lt;span class="w"&gt; &lt;/span&gt;orange
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;throws&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;leading&lt;span class="w"&gt; &lt;/span&gt;black&lt;span class="w"&gt; &lt;/span&gt;band
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;12K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;brown,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;orange
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;32K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;orange,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;orange
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;42K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;yellow,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;orange&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;52K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;green,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;orange
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;62K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;blue,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;orange
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;72K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;violet,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;orange
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;82K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grey,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;orange
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;92K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;white,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;orange
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;22R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;black
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;220R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;brown
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;2K2&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;red
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;220K&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;yellow&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;2M2&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;green
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;22M&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;blue
&lt;span class="w"&gt;      &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;returns&lt;span class="w"&gt; &lt;/span&gt;220M&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;red,&lt;span class="w"&gt; &lt;/span&gt;violet

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;33&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;33&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.363&lt;span class="w"&gt; &lt;/span&gt;s,&lt;span class="w"&gt; &lt;/span&gt;estimated&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: most of the service-level tests take less than 1ms (so the time isn't reported), whereas all of the API-level tests take more. This is another reason it's useful to keep business logic independent of the transport layer (and others, e.g. a persistence layer for talking to a database) - testing the basic logical code is generally much faster than dealing with the frameworks and connections that come with those other layers.&lt;/p&gt;
&lt;p&gt;Once everything's passing, make a commit.&lt;/p&gt;
&lt;h2&gt;Four bands [7/9]&lt;/h2&gt;
&lt;p&gt;We can handle all valid one- and three-band resistors at this point, plus some invalid one- and two-band cases. So let's handle resistors with &lt;em&gt;three&lt;/em&gt; value bands, adding an extra significant figure to the value.&lt;/p&gt;
&lt;p&gt;Again it's important to think about the cases we're going to choose to ensure our code works correctly. I would suggest at least three, based on the &lt;em&gt;structure&lt;/em&gt; of the output:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Where the multiplier is a multiple of 3 (0, 3, 6, 9), i.e. the band is black, orange, blue or white, we already showed three digits, e.g. &lt;code&gt;"120R"&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Otherwise, we only showed two digits before, e.g. &lt;code&gt;"12K"&lt;/code&gt; or &lt;code&gt;"1M2"&lt;/code&gt;, so we're adding a third digit;&lt;/li&gt;
&lt;li&gt;Unless the third value band is black, in which case we still shouldn't show a trailing zero.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here the cases we've selected have a meaning, so the name should clarify that meaning to the reader rather than just e.g. &lt;code&gt;"returns 123K for brown, red, orange, orange"&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;four bands&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;adds a third digit in the middle&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;brown&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;red&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;orange&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;orange&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;123K&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;adds a third digit at the end&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;brown&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;yellow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;violet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;brown&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1K47&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;does not add a trailing zero&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resistance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;grey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;green&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;68M&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Introduce these tests (along with an API integration case, if you like), get everything passing and make a commit.&lt;/p&gt;
&lt;h2&gt;Paradox of tolerance [8/9]&lt;/h2&gt;
&lt;p&gt;Now we're going to add a fourth rule of resistors:&lt;/p&gt;
&lt;ol start="4"&gt;
  &lt;li&gt;A resistor may have a &lt;em&gt;tolerance&lt;/em&gt; band (otherwise its tolerance is ±20%), unless it's a 0Ω resistor.
&lt;/ol&gt;

&lt;p&gt;We'll cover five possible cases here, which include two new band colours and reuse two of the existing colours:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;±20%&lt;/th&gt;
&lt;th&gt;±10%&lt;/th&gt;
&lt;th&gt;±5%&lt;/th&gt;
&lt;th&gt;±2%&lt;/th&gt;
&lt;th&gt;±1%&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;No band&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: gold; background-color: black"&gt;&amp;nbsp;gold&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: silver; background-color: black"&gt;&amp;nbsp;silver&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span style="color: brown"&gt;brown&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is, as you may just have realised, a bit of a problem. If the tolerance band is optional, then what is e.g. &lt;strong&gt;&lt;span style="color: brown"&gt;brown&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: green"&gt;green&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: yellow; background-color: black"&gt;&amp;nbsp;yellow&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;span style="color: red"&gt;red&lt;/span&gt;&lt;/strong&gt; describing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;15,400Ω ±20%; or&lt;/li&gt;
&lt;li&gt;150,000Ω ±2%?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Obviously that's quite a big difference; the circuit probably isn't going to work correctly if you use the wrong one! On the physical packaging this is indicated by a gap - the value and multiplier bands are at one end of the resisistor, the tolerance band is at the other. Perhaps we could do something similar, adding a separate parameter at the service level and a separate query parameter to the HTTP API? For example, maybe something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://localhost:3000/resistance?bands=brown&amp;amp;bands=green&amp;amp;bands=yellow&amp;amp;bands=red&amp;#39;&lt;/span&gt;
15K4&lt;span class="w"&gt; &lt;/span&gt;±20%
$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://localhost:3000/resistance?bands=brown&amp;amp;bands=green&amp;amp;bands=yellow&amp;amp;tolerance=red&amp;#39;&lt;/span&gt;
150K&lt;span class="w"&gt; &lt;/span&gt;±2%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here we're changing the responses for existing requests - now rather than &lt;code&gt;"150K"&lt;/code&gt;, we get &lt;code&gt;"150K ±20%"&lt;/code&gt;. I'd suggest making this change first, as a separate commit, then moving on to include the actual tolerance bands. It's still &lt;code&gt;"0R"&lt;/code&gt; for a single black band, a 0Ω resistor never has a tolerance band.&lt;/p&gt;
&lt;p&gt;Design the API and test-drive the implementation of your choice, starting with an integration test then driving out the full functionality through some unit tests.&lt;/p&gt;
&lt;p&gt;Once you're happy, make a final commit - we're done!&lt;/p&gt;
&lt;h2&gt;Exercises [9/9]&lt;/h2&gt;
&lt;p&gt;Here are some follow-up tasks for further practice (remember to &lt;strong&gt;test-drive&lt;/strong&gt; anything you work on):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Predict and then check happens if you make a request where the bands aren't recognised colours (e.g. &lt;code&gt;GET /resistance?bands=fuchsia&amp;amp;bands=goldenrod&amp;amp;bands=octarine&lt;/code&gt;) and/or there are multiple tolerance bands. Did you predict correctly? Do you think it's the &lt;em&gt;right&lt;/em&gt; behaviour - do you consider that request to be &lt;em&gt;semantically&lt;/em&gt; or &lt;em&gt;structurally&lt;/em&gt; invalid, and does the current implementation reflect that? If you think it should behave differently, update accordingly.&lt;/li&gt;
&lt;li&gt;Return to step 4 and try out some different orders for introducing the three-band cases - did I suggest the right route, how much difference does it make?&lt;/li&gt;
&lt;li&gt;Design and develop a different HTTP API (i.e. changing any or all of the request method, request path, use of query parameters or structure of the response body).&lt;/li&gt;
&lt;li&gt;As well as the &lt;em&gt;value&lt;/em&gt;, &lt;em&gt;multiplier&lt;/em&gt; and &lt;em&gt;tolerance&lt;/em&gt; bands, resistors may have a &lt;em&gt;temperature coefficient&lt;/em&gt; band - implement support for this.&lt;/li&gt;
&lt;li&gt;There's a set of &lt;a href="https://en.wikipedia.org/wiki/E_series_of_preferred_numbers"&gt;preferred numbers&lt;/a&gt; that resistors are generally designed to (e.g. for the default ±20% tolerance you'd get resistors only in multiples of 1.0, 1.5, 2.2, 3.3, 4.7 or 6.8) - introduce a "strict" mode in which non-preferred resistors are invalid inputs.&lt;/li&gt;
&lt;li&gt;Write a CLI to expose the core functionality on the command line (access any arguments via &lt;code&gt;process.argv&lt;/code&gt;; you can use Node's built-in &lt;a href="https://nodejs.org/dist/latest/docs/api/util.html#utilparseargsconfig"&gt;&lt;code&gt;parseArgs&lt;/code&gt;&lt;/a&gt;, available from v16.17/v18.3, to help you out if you want to allow some non-positional arguments e.g. &lt;code&gt;node cli.js red green blue --strict&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Write a React app that consumes the HTTP API to allow a user to interactively determine the shorthand for a given set of bands - as you do so you may realise you need to change the API to support the UI, feel free to do so and read up on &lt;em&gt;consumer-driven&lt;/em&gt; API development.&lt;/li&gt;
&lt;li&gt;Try writing the tests with a different HTTP client (e.g. Axios, fetch) instead of Supertest.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I'd recommend creating a new git branch for each one you try (e.g. use &lt;code&gt;git checkout -b &amp;lt;name&amp;gt;&lt;/code&gt;) and making commits as appropriate.&lt;/p&gt;</content><category term="development"></category><category term="javascript"></category><category term="tdd"></category><category term="xp"></category></entry><entry><title>Go directly to (pairing) jail</title><link href="https://blog.jonrshar.pe/2021/Nov/06/pairing-jail.html" rel="alternate"></link><published>2021-11-06T19:00:00+00:00</published><updated>2021-11-07T23:15:00+00:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2021-11-06:/2021/Nov/06/pairing-jail.html</id><summary type="html">&lt;p&gt;Planning pair rotations to maximise context, diverse ideas, shared ownership, and team relationships.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In my &lt;a href="https://blog.jonrshar.pe/2017/Oct/13/ada-college-pairing.html"&gt;introduction to pairing&lt;/a&gt; I wrote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Generally we rotate [pairs] on a daily basis, using tools like &lt;a href="https://parrit.io/"&gt;Parrit&lt;/a&gt; to make sure that every possible combination is occurring. If a story is still in flight from the previous day we &lt;em&gt;"stick and twist"&lt;/em&gt;, with one person staying with the story to pass along the context and the other moving on to something else. This has the natural side effect that the more complex stories, the ones that take multiple days, get more people's input.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Context is certainly important here, we want to have a degree of consistency on a given track, so we minimise time spent re-learning information we already had within the team. However there are a few other things we're trying to maximise when choosing when and how to rotate the pairs in the team, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Diverse ideas&lt;/strong&gt; - different people's cultural and technical backgrounds mean they solve problems in different ways, the more different ideas we bring in the more likely it is we'll find the best one;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shared ownership&lt;/strong&gt; - we want everyone to feel a collective responsibility for the whole codebase, so they're comfortable to refactor towards higher quality; and&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Team relationships&lt;/strong&gt; - we want to give everyone in the team the chance to work with and get to know everyone else, especially if they're new.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You could maximise context retained by keeping one person working on the same track of work (this might be a single complex story, and epic comprising multiple stories, or work that's grouped due to e.g. domain) indefinitely, but that would risk missing these other objectives. Therefore we want to strike a balance between building up the context and actively sharing it around the team, which we can do through mindful pair rotations.&lt;/p&gt;
&lt;p&gt;To support these rotations, some teams I've worked on have found it useful to maintain a &lt;em&gt;pairing table&lt;/em&gt;, visualising how long people have remained on the same track. Each track has a row and there are columns for the number of consecutive days spent working on that track (usually up to five). The last space on each track is "jail" (and you don't want to end up in &lt;a href="https://en.wikipedia.org/wiki/Monopoly_(game)#Jail"&gt;jail&lt;/a&gt;, you don't pass Go or collect £200!)&lt;/p&gt;
&lt;p&gt;In line with the Extreme Programming (XP) practice of the &lt;em&gt;Informative Workspace&lt;/em&gt;, this would usually be posted up somewhere in the team's working area, e.g. a grid of painter's tape on a whiteboard with each developer on the team represented by an Instax photograph of them (these tend to be a bit more durable than just using stickies, and make it easier for visitors to identify the people they need to speak to about a given track). In these pandemic-influenced days you can achieve a similar effect in e.g. a Miro board (&lt;strong&gt;pro-tip&lt;/strong&gt;: use the &lt;a href="https://help.miro.com/hc/en-us/articles/360011986519-Tables"&gt;table widget&lt;/a&gt; to generate the structure easily):&lt;/p&gt;
&lt;p&gt;&lt;img alt="Illustration of a pairing table created in Miro" src="https://blog.jonrshar.pe/images/pairing-table.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;small&gt;Created in &lt;a href="https://miro.com/"&gt;Miro&lt;/a&gt;. (Almost-)too-cute-to-eat dim sum by &lt;a href="https://deniseyu.io/"&gt;Denise Yu&lt;/a&gt;, released under &lt;a href="https://creativecommons.org/licenses/by-sa/4.0/"&gt;CC BY-SA 4.0&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;
&lt;hr&gt;

&lt;p&gt;To see how this can be used in practice, let's run through a week on a 3-pair project. We'd wrapped up everything that had been in flight at the end of the last iteration, so everyone's starting on day 1 of their respective tracks:&lt;/p&gt;
&lt;table style="table-layout: fixed; width: 100%"&gt;
    &lt;thead&gt;
        &lt;tr&gt;&lt;th&gt;Track&lt;/th&gt;&lt;th&gt;🟢 Day 1&lt;/th&gt;&lt;th&gt;🟡 Day 2&lt;/th&gt;&lt;th&gt;🟠 Day 3&lt;/th&gt;&lt;th&gt;🔴 Day 4&lt;/th&gt;&lt;th&gt;🔥 Day 5&lt;/th&gt;&lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Alpha&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Albert / Basti&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bravo&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Carol / Daniel&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Charlie&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Ethel / Farah&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr&gt;

&lt;p&gt;When pairing up on Tuesday, we want to ensure that any context from Monday is kept in each track, so we &lt;em&gt;"stick"&lt;/em&gt; one person and &lt;em&gt;"twist"&lt;/em&gt; the other on each track:&lt;/p&gt;
&lt;table style="table-layout: fixed; width: 100%"&gt;
    &lt;thead&gt;
        &lt;tr&gt;&lt;th&gt;Track&lt;/th&gt;&lt;th&gt;🟢 Day 1&lt;/th&gt;&lt;th&gt;🟡 Day 2&lt;/th&gt;&lt;th&gt;🟠 Day 3&lt;/th&gt;&lt;th&gt;🔴 Day 4&lt;/th&gt;&lt;th&gt;🔥 Day 5&lt;/th&gt;&lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Alpha&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Ethel&lt;/td&gt;&lt;td&gt;Albert&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bravo&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Basti&lt;/td&gt;&lt;td&gt;Daniel&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Charlie&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Carol&lt;/td&gt;&lt;td&gt;Farah&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr&gt;

&lt;p&gt;On Wednesday, Ethel's out doing some training, so Albert continues on track Alpha to keep context there. We decide that Carol can solo on track Charlie, and rotate everyone else accordingly:&lt;/p&gt;
&lt;table style="table-layout: fixed; width: 100%"&gt;
    &lt;thead&gt;
        &lt;tr&gt;&lt;th&gt;Track&lt;/th&gt;&lt;th&gt;🟢 Day 1&lt;/th&gt;&lt;th&gt;🟡 Day 2&lt;/th&gt;&lt;th&gt;🟠 Day 3&lt;/th&gt;&lt;th&gt;🔴 Day 4&lt;/th&gt;&lt;th&gt;🔥 Day 5&lt;/th&gt;&lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Alpha&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Daniel&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Albert&lt;td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bravo&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Farah&lt;/td&gt;&lt;td&gt;Basti&lt;/td&gt;&lt;td&gt;&lt;/td &gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Charlie&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Carol&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr&gt;

&lt;p&gt;On Thursday, although Ethel's back, Daniel's sadly feeling under the weather.
That means Albert moves to day 4 of the track, so we definitely need to give
him a pair to let him rotate out before reaching day 5. We don't want to let
Carol continue soloing otherwise she'll end up on day 4 too, so Farah
solos on track Bravo.&lt;/p&gt;
&lt;table style="table-layout: fixed; width: 100%"&gt;
    &lt;thead&gt;
        &lt;tr&gt;&lt;th&gt;Track&lt;/th&gt;&lt;th&gt;🟢 Day 1&lt;/th&gt;&lt;th&gt;🟡 Day 2&lt;/th&gt;&lt;th&gt;🟠 Day 3&lt;/th&gt;&lt;th&gt;🔴 Day 4&lt;/th&gt;&lt;th&gt;🔥 Day 5&lt;/th&gt;&lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Alpha&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Basti&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Albert&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bravo&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Farah&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Charlie&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Ethel&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Carol&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr&gt;

&lt;p&gt;Daniel's still out on Friday. Albert rotates out of track Alpha as planned,
and as Farah was soloing yesterday we want to make sure they have a pair today:&lt;/p&gt;
&lt;table style="table-layout: fixed; width: 100%"&gt;
    &lt;thead&gt;
        &lt;tr&gt;&lt;th&gt;Track&lt;/th&gt;&lt;th&gt;🟢 Day 1&lt;/th&gt;&lt;th&gt;🟡 Day 2&lt;/th&gt;&lt;th&gt;🟠 Day 3&lt;/th&gt;&lt;th&gt;🔴 Day 4&lt;/th&gt;&lt;th&gt;🔥 Day 5&lt;/th&gt;&lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Alpha&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Carol&lt;/td&gt;&lt;td&gt;Basti&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bravo&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Albert&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Farah&lt;/td
&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Charlie&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Ethel&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr&gt;

&lt;p&gt;Despite some planned and unplanned absences, we've got to the end of the week without anyone ending up in "jail"! Every day we've had at least one person stay in each track, to bring a consistent context. Every track has had at least three different people working on it during the week, encouraging shared ownership of the whole codebase and ensuring lots of different ideas are brought into each track. And in terms of building relationships, if we think about who has paired with whom, we've achieved a good balance throughout the team; the only pairings that &lt;em&gt;haven't&lt;/em&gt; been made during the week are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Albert and Carol;&lt;/li&gt;
&lt;li&gt;Basti and Ethel;&lt;/li&gt;
&lt;li&gt;Daniel and Ethel; and&lt;/li&gt;
&lt;li&gt;Daniel and Farah.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This isn't to say that there will never be times when you want someone to stay on one track of work for five or more days, but that should really be the exception rather than the rule; you can use this heuristic to be intentful about whether or not that should be happening in your current context.&lt;/p&gt;
&lt;p&gt;We also use other practices that support working in this way, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pivotaltracker.com/blog/how-to-invest-in-your-user-stories"&gt;INVEST&lt;/a&gt; user stories encourage small steps with clear acceptance criteria;&lt;/li&gt;
&lt;li&gt;Test-driven development (TDD) means that any work in flight has some tests to guide you towards the planned implementation; and&lt;/li&gt;
&lt;li&gt;Trunk-based development pushes you towards small commits and frequent integration  (branches are generally only used to store our progress on the remote overnight, so we're not reliant on any single hard drive continuing to work).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This way, even if there was work in flight on a given track yesterday and there's nobody in today who worked on it, you have a high quality user story, failing test(s) to start from and small diff to understand. This keeps the team resilient and able to make progress.&lt;/p&gt;</content><category term="work"></category><category term="pairing"></category><category term="xp"></category></entry><entry><title>JS TDD API</title><link href="https://blog.jonrshar.pe/2021/Apr/10/js-tdd-api.html" rel="alternate"></link><published>2021-04-10T20:30:00+01:00</published><updated>2023-12-23T14:30:00+00:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2021-04-10:/2021/Apr/10/js-tdd-api.html</id><summary type="html">&lt;p&gt;Test-driven JavaScript development done right - part 3&lt;/p&gt;</summary><content type="html">&lt;p&gt;Welcome to part 3 of this ongoing series on test-driven development (TDD) in JavaScript. So far we've covered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://blog.jonrshar.pe/2020/Aug/31/js-tdd-ftw.html"&gt;Part 1&lt;/a&gt;&lt;/strong&gt; - the basic principles of test-driven development, showing unit testing using &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://blog.jonrshar.pe/2020/Nov/22/js-tdd-e2e.html"&gt;Part 2&lt;/a&gt;&lt;/strong&gt; - expanding to higher level testing, using &lt;a href="https://cypress.io"&gt;Cypress&lt;/a&gt; for E2E and Jest for integration (and unit) testing of a simple React app with &lt;a href="https://testing-library.com"&gt;Testing Library&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the second part, I covered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How to add Cypress tests to a basic &lt;a href="https://create-react-app.dev/docs/getting-started"&gt;Create React App&lt;/a&gt; project&lt;/li&gt;
&lt;li&gt;Outside-in TDD, working from E2E tests in Cypress; through integration tests of multiple components working together; down to the unit level of individual components&lt;/li&gt;
&lt;li&gt;Testing &lt;em&gt;presentation&lt;/em&gt; components (&lt;code&gt;&amp;lt;Outcome result={...} /&amp;gt;&lt;/code&gt;) by rendering them with different props and checking what gets displayed in the DOM&lt;/li&gt;
&lt;li&gt;Testing &lt;em&gt;interaction&lt;/em&gt; components (&lt;code&gt;&amp;lt;Form onSubmit={...} /&amp;gt;&lt;/code&gt;) by passing &lt;em&gt;test doubles&lt;/em&gt; for their callbacks and checking they get called appropriately on simulated user interaction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you haven't read these yet, I'd suggest you go back and run through them before getting stuck into this one. Make sure you're confident with the approach so far, because we're going to add in a few new ideas here.&lt;/p&gt;
&lt;p&gt;So what's next? Our rock, paper, scissors (RPS) game isn't very much fun right now, because the second player can guarantee a win by waiting to see what the first player throws and choosing their weapon accordingly. Instead, let's make it a one-player game where you're taking on a random user:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Mockup of the proposed RPS UI" src="https://blog.jonrshar.pe/images/rps-api-ui.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;Created with &lt;a href="https://remarkable.com/"&gt;reMarkable&lt;/a&gt;.&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Here the &lt;code&gt;???&lt;/code&gt;s indicate information that will be random - we don't know the &lt;em&gt;name&lt;/em&gt; of the opponent or which &lt;em&gt;weapon&lt;/em&gt; they'll choose and therefore what the &lt;em&gt;outcome&lt;/em&gt; will be. That's part of the fun! But it also makes it a little trickier to test.&lt;/p&gt;
&lt;p&gt;This also gives us the opportunity to practice interacting with an API; making requests and handling responses, and seeing how to test that at various levels. Again this adds complexity, this time because it's random and because it's &lt;em&gt;asynchronous&lt;/em&gt;. We will get the opponent data from &lt;code&gt;https://randomuser.me/&lt;/code&gt;, a really handy API for exactly this kind of exercise.&lt;/p&gt;
&lt;p&gt;Before we continue, think about how you might actually implement that UI in React - what components would you have, how would they interact, where would the state live? Note your ideas down, we'll revisit them later.&lt;/p&gt;
&lt;h3&gt;Requirements&lt;/h3&gt;
&lt;p&gt;The prerequisites here are the same as the previous articles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;*nix command line: already provided on macOS and Linux; if you're using Windows try &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/about"&gt;WSL&lt;/a&gt; or &lt;a href="https://gitforwindows.org/"&gt;Git BASH&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/"&gt;Node&lt;/a&gt; (16+ recommended, Jest 29 &lt;a href="https://jestjs.io/docs/upgrading-to-jest29#compatibility"&gt;dropped support&lt;/a&gt; for Node 12; run &lt;code&gt;node -v&lt;/code&gt; to check) and NPM; and&lt;/li&gt;
&lt;li&gt;Familiarity with ES6 JavaScript syntax.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition, given the domain for this post, you'll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Familiarity with React development - I'm going to assume you know how to write a basic implementation, guiding you with test cases and a few function component examples.&lt;/li&gt;
&lt;li&gt;Familiarity with promises - the asynchronous parts will use &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; syntax, and Cypress uses &lt;code&gt;.then&lt;/code&gt; in a similar way to promises.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Again please carefully &lt;em&gt;read everything&lt;/em&gt;, and for newer developers I'd recommend &lt;em&gt;typing the code&lt;/em&gt; rather than copy-pasting.&lt;/p&gt;
&lt;h2&gt;Setup [1/8]&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: to use Vite, a more up-to-date alternative to CRA, see
&lt;a href="https://blog.jonrshar.pe/2023/Dec/17/js-tdd-vite.html"&gt;supplement A&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let's create a new CRA-with-Cypress project. You can return to part 2 for more detailed instructions, or follow the steps below if you're feeling more confident (I've also published instructions and even a script to automate the process in &lt;a href="https://gist.github.com/textbook/3377dda14efe4449772c2377188c3fa8"&gt;this Gist&lt;/a&gt;):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a new React app with &lt;code&gt;npx create-react-app@latest rps-api&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Enter the project directory with &lt;code&gt;cd rps-api/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add Cypress and the relevant Testing Library utilities with &lt;code&gt;npm install cypress @testing-library/{cypress,react,user-event}@latest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Open the Cypress UI to do the initial setup with &lt;code&gt;npx cypress open&lt;/code&gt;, select "E2E Testing" then quit the UI&lt;/li&gt;
&lt;li&gt;Remove the example fixture with &lt;code&gt;rm ./cypress/fixtures/example.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Configure Cypress to look for the CRA app and not create videos by adding &lt;code&gt;baseUrl: "http://localhost:3000"&lt;/code&gt; and &lt;code&gt;video: false&lt;/code&gt; into the &lt;code&gt;e2e&lt;/code&gt; object in &lt;code&gt;cypress.config.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Set up the Testing Library utilities by adding &lt;code&gt;import "@testing-library/cypress/add-commands";&lt;/code&gt; to &lt;code&gt;./cypress/support/commands.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Exclude any files Cypress generates from your commits by adding &lt;code&gt;cypress/downloads/&lt;/code&gt;, &lt;code&gt;cypress/screenshots/&lt;/code&gt; and &lt;code&gt;cypress/videos/&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add a command to run the E2E tests by adding &lt;code&gt;"e2e": "cypress run"&lt;/code&gt; into the &lt;code&gt;"scripts"&lt;/code&gt; object in &lt;code&gt;package.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Create a new test file with &lt;code&gt;touch ./cypress/e2e/journey.cy.js&lt;/code&gt; (you may need to &lt;code&gt;mkdir ./cypress/e2e&lt;/code&gt; first)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;[Optional]&lt;/em&gt; Install the ESLint plugin with &lt;code&gt;npm install eslint-plugin-cypress&lt;/code&gt; and add the following to the &lt;code&gt;eslintConfig&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt;:&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;quot;overrides&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;extends&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;plugin:cypress/recommended&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;files&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cypress/**/*.js&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you've done all of the above correctly, you should be able to run &lt;code&gt;npm start&lt;/code&gt; in one terminal window and then &lt;code&gt;npm run e2e&lt;/code&gt; in another, giving the following output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;e2e&lt;span class="w"&gt;                    &lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-api@0.1.0&lt;span class="w"&gt; &lt;/span&gt;e2e
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;cypress&lt;span class="w"&gt; &lt;/span&gt;run


DevTools&lt;span class="w"&gt; &lt;/span&gt;listening&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;ws://127.0.0.1:55431/devtools/browser/75378efa-5dd2-45b5-b0c0-91fb862e7076
Couldn&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;tsconfig.json.&lt;span class="w"&gt; &lt;/span&gt;tsconfig-paths&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;skipped&lt;/span&gt;

&lt;span class="o"&gt;====================================================================================&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;Starting&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Cypress:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;.16.0&lt;span class="w"&gt;                                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Browser:&lt;span class="w"&gt;        &lt;/span&gt;Electron&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;106&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;headless&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Node&lt;span class="w"&gt; &lt;/span&gt;Version:&lt;span class="w"&gt;   &lt;/span&gt;v16.20.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/node&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Specs:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;journey.cy.js&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Searched:&lt;span class="w"&gt;       &lt;/span&gt;cypress/e2e/**/*.cy.&lt;span class="o"&gt;{&lt;/span&gt;js,jsx,ts,tsx&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt;                                            &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────────────────────────────────

&lt;span class="w"&gt;  &lt;/span&gt;Running:&lt;span class="w"&gt;  &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                                                                   &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passing&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;0ms&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Results&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tests:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Passing:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Failing:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Pending:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Skipped:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Screenshots:&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Video:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;                                                                            &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Duration:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;seconds&lt;span class="w"&gt;                                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Spec&lt;span class="w"&gt; &lt;/span&gt;Ran:&lt;span class="w"&gt;     &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                                                                    &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘


&lt;span class="o"&gt;====================================================================================&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;Finished&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="w"&gt;       &lt;/span&gt;Spec&lt;span class="w"&gt;                                              &lt;/span&gt;Tests&lt;span class="w"&gt;  &lt;/span&gt;Passing&lt;span class="w"&gt;  &lt;/span&gt;Failing&lt;span class="w"&gt;  &lt;/span&gt;Pending&lt;span class="w"&gt;  &lt;/span&gt;Skipped&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;✔&lt;span class="w"&gt;  &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                              &lt;/span&gt;0ms&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘
&lt;span class="w"&gt;    &lt;/span&gt;✔&lt;span class="w"&gt;  &lt;/span&gt;All&lt;span class="w"&gt; &lt;/span&gt;specs&lt;span class="w"&gt; &lt;/span&gt;passed!&lt;span class="w"&gt;                          &lt;/span&gt;0ms&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you don't see that output, go back and double-check you've followed all of the steps as written. If you do, great! Make a commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;.gitignore
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;cypress.config.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;cypress/e2e/journey.cy.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;cypress/plugins/index.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;cypress/support/commands.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;cypress/support/index.js
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;package-lock.json
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;package.json


$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Install and configure Cypress&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;9d1af26&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Install&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;configure&lt;span class="w"&gt; &lt;/span&gt;Cypress
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2242&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cypress.config.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cypress/e2e/journey.cy.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cypress/plugins/index.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cypress/support/commands.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cypress/support/index.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Dealing with unknowns [2/8]&lt;/h2&gt;
&lt;p&gt;Based on the above UI sketch, the way our new RPS game should work is as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You are always the "left" player, with the same drop-down to select  a weapon as before&lt;/li&gt;
&lt;li&gt;Your opponent is always the "right" player, a computer player with random personal details (we'll show their avatar and name)&lt;/li&gt;
&lt;li&gt;When you throw, the computer player picks a random weapon&lt;/li&gt;
&lt;li&gt;The outcome is displayed exactly as before ("Left wins!", "Right wins!" or "Draw")&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm going to split that into two tests in &lt;code&gt;cypress/e2e/journey.cy.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;displays a random opponent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;contain.text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* ??? */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-avatar&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;have.attr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;src&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* ??? */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;displays the appropriate winner&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Throw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-weapon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;contain.text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* ??? */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;outcome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;contain.text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* ??? */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Immediately we can see a problem - if the opponent and their throw are random, we don't know what the &lt;em&gt;values&lt;/em&gt; are going to be. I'll explore three different ways to deal with this.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Broaden expectations&lt;/strong&gt; - we could use less specific expectations to test that the &lt;em&gt;shape&lt;/em&gt; of the output is appropriate, without checking the details of the &lt;em&gt;values&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;displays an opponent from the API&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;match&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sr"&gt;/[A-Z][a-z]+ [A-Z][a-z]+/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-avatar&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;have.attr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;src&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;match&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sr"&gt;/^https:\/\/randomuser\.me\/api\/portraits/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you're unfamiliar with the regular expression (&lt;em&gt;"regex"&lt;/em&gt;) syntax used in &lt;code&gt;match&lt;/code&gt;, you can paste those patterns into e.g. &lt;a href="https://regex101.com/"&gt;Regex 101&lt;/a&gt; to get an explanation, but basically this says that the name should be two words, each starting with a capital letter, and the avatar's source should start like a URL. This means we don't have to know exactly what data we'll get back from the API to make an assertion on it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; that this isn't a very thorough regex, and people's names take &lt;a href="https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/"&gt;a lot of forms&lt;/a&gt; not considered by it. While writing this article I got one assertion error due to it failing to match an entirely valid name:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AssertionError: Timed out retrying after 4000ms: expected 'Maïwenn Gaillard' to match /[A-Z][a-z]+ [A-Z][a-z]+/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;I'm using it here as an example to show how you can provide a "fuzzier" match for uncontrolled data, it shouldn't be used for e.g. input validation in a real system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fake data&lt;/strong&gt; - for sources of unknown data &lt;em&gt;outside&lt;/em&gt; of our application, that we're getting from APIs, we can provide known fake data. Cypress 6 &lt;a href="https://www.cypress.io/blog/2020/11/24/introducing-cy-intercept-next-generation-network-stubbing-in-cypress-6-0/"&gt;introduced&lt;/a&gt; &lt;em&gt;"next generation network stubbing"&lt;/em&gt;, so I thought it would be interesting to show how we can use that, but note there are lots of other ways to do this (see e.g. &lt;a href="https://blog.jonrshar.pe/2020/Sep/19/spa-config.html"&gt;this post&lt;/a&gt; on how to configure single-page apps, letting you fetch data from a different API for testing).&lt;/p&gt;
&lt;p&gt;To make sure our example is as realistic as possible, let's get it directly from the API we're going to use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;https://api.randomuser.me&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;./cypress/fixtures/example.json
&lt;span class="w"&gt;  &lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;Total&lt;span class="w"&gt;    &lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;Received&lt;span class="w"&gt; &lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;Xferd&lt;span class="w"&gt;  &lt;/span&gt;Average&lt;span class="w"&gt; &lt;/span&gt;Speed&lt;span class="w"&gt;   &lt;/span&gt;Time&lt;span class="w"&gt;    &lt;/span&gt;Time&lt;span class="w"&gt;     &lt;/span&gt;Time&lt;span class="w"&gt;  &lt;/span&gt;Current
&lt;span class="w"&gt;                                 &lt;/span&gt;Dload&lt;span class="w"&gt;  &lt;/span&gt;Upload&lt;span class="w"&gt;   &lt;/span&gt;Total&lt;span class="w"&gt;   &lt;/span&gt;Spent&lt;span class="w"&gt;    &lt;/span&gt;Left&lt;span class="w"&gt;  &lt;/span&gt;Speed
&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1167&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1167&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;5984&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--:--:--&lt;span class="w"&gt; &lt;/span&gt;--:--:--&lt;span class="w"&gt; &lt;/span&gt;--:--:--&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5984&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;./cypress/fixtures/example.json
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;results&amp;quot;&lt;/span&gt;:&lt;span class="o"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gender&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;male&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;Mr&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;first&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;Randy&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;last&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;Wheeler&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;location&amp;quot;&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;street&amp;quot;&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;number&amp;quot;&lt;/span&gt;:9524,&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;Forest Ln&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;city&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;Knoxville&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;state&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;North Dakota&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;country&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;United States&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;postcode&amp;quot;&lt;/span&gt;:66112,&lt;span class="s2"&gt;&amp;quot;coordinates&amp;quot;&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;latitude&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;83.6453&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;longitude&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;-96.6784&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;timezone&amp;quot;&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;offset&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;-1:00&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;Azores, Cape Verde Islands&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;email&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;randy.wheeler@example.com&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;login&amp;quot;&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;uuid&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;cf6f47a9-05a0-48c7-b09b-f7f25c1fb43f&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;username&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;organicpeacock969&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;password&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;1103&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;salt&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;cuNdIWCC&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;md5&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;34329408fbe3f2b9e8e06ca4d9e03eec&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;sha1&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;c68b20496e5d747f5b6b2ea75852a4dc8b8e87f0&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;sha256&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;85a51818e94cf3e751fe76dff562d6047c261e9a6911994156b5c90176eb2580&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;dob&amp;quot;&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;date&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;1970-11-28T01:34:37.972Z&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;age&amp;quot;&lt;/span&gt;:50&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;registered&amp;quot;&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;date&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;2018-10-14T00:27:46.788Z&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;age&amp;quot;&lt;/span&gt;:2&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;phone&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;(370)-972-2945&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;cell&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;(464)-808-2579&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;SSN&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;value&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;721-97-7603&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;picture&amp;quot;&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;large&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;https://randomuser.me/api/portraits/men/12.jpg&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;medium&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;https://randomuser.me/api/portraits/med/men/12.jpg&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;thumbnail&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;https://randomuser.me/api/portraits/thumb/men/12.jpg&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;nat&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;US&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}]&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;info&amp;quot;&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;seed&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;133d55784bdd75c9&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;results&amp;quot;&lt;/span&gt;:1,&lt;span class="s2"&gt;&amp;quot;page&amp;quot;&lt;/span&gt;:1,&lt;span class="s2"&gt;&amp;quot;version&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;1.3&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that your data will most likely be different, but I'll use the data shown above for the rest of the article. We can now tell Cypress to respond to the request with the data stored in our fixture, then assert on the values from the fixture:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;displays an opponent from a fixture&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intercept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://api.randomuser.me&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;example.json&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;contain.text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Randy Wheeler&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-avatar&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;have.attr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;src&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://randomuser.me/api/portraits/thumb/men/12.jpg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; that this approach is not without risk - if the API we're mocking changes such that the content of &lt;code&gt;example.json&lt;/code&gt; doesn't match the response, our tests will still pass but the code &lt;em&gt;won't work&lt;/em&gt; in real life.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reimplement logic&lt;/strong&gt; - for sources of unknown data &lt;em&gt;inside&lt;/em&gt; our application, usually some kind of randomness, we may end up reimplementing some of the logic we're testing. Here we know that the left player will throw &lt;code&gt;"rock"&lt;/code&gt;, because we control this, but right player could throw either &lt;code&gt;"rock"&lt;/code&gt;, &lt;code&gt;"paper"&lt;/code&gt; &lt;em&gt;or&lt;/em&gt; &lt;code&gt;"scissors"&lt;/code&gt;. Therefore there are three possible outcomes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OUTCOMES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Draw!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// rock draws with rock&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Right wins!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// paper wraps rock&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Left wins!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// scissors are blunted by rock&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Although we can't tell in advance which will be randomly picked, once we see the opponent's choice we know what the outcome should be. So we can use Cypress's &lt;a href="https://docs.cypress.io/guides/core-concepts/variables-and-aliases.html#Closures"&gt;closures&lt;/a&gt;, an API very similar to &lt;em&gt;promises&lt;/em&gt;, to get access to the opponent's choice and use that to determine the expected outcome and make the appropriate assertion:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;displays the appropriate winner&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Throw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-weapon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;$weapon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;outcome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;contain.text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OUTCOMES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;$weapon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; that if you're seeing ESLint warnings it will be unhappy with the last few lines, even if you added the Cypress configuration in step 11 above. One of the Jest-specific rules doesn't understand how the Cypress closures work, so you'll need to explicitly disable it by adding &lt;code&gt;"rules": {"jest/valid-expect-in-promise": "off"}&lt;/code&gt; into the override for Cypress files.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By combining these three approaches, we can be confident that our app works correctly despite the uncertainty around the specific values we'll be getting. Call the shots and run the tests:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;e2e

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-api@0.1.0&lt;span class="w"&gt; &lt;/span&gt;e2e
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;cypress&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;


&lt;span class="o"&gt;====================================================================================&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;Starting&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Cypress:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;.16.0&lt;span class="w"&gt;                                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Browser:&lt;span class="w"&gt;        &lt;/span&gt;Electron&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;106&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;headless&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Node&lt;span class="w"&gt; &lt;/span&gt;Version:&lt;span class="w"&gt;   &lt;/span&gt;v16.20.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/node&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Specs:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;journey.cy.js&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Searched:&lt;span class="w"&gt;       &lt;/span&gt;cypress/e2e/**/*.cy.&lt;span class="o"&gt;{&lt;/span&gt;js,jsx,ts,tsx&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt;                                            &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────────────────────────────────

&lt;span class="w"&gt;  &lt;/span&gt;Running:&lt;span class="w"&gt;  &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                                                                   &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;displays&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;opponent&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;API
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;displays&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;opponent&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;fixture
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;displays&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;appropriate&lt;span class="w"&gt; &lt;/span&gt;winner

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passing&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;13s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failing

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;displays&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;opponent&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;API:
&lt;span class="w"&gt;     &lt;/span&gt;AssertionError:&lt;span class="w"&gt; &lt;/span&gt;Timed&lt;span class="w"&gt; &lt;/span&gt;out&lt;span class="w"&gt; &lt;/span&gt;retrying&lt;span class="w"&gt; &lt;/span&gt;after&lt;span class="w"&gt; &lt;/span&gt;4000ms:&lt;span class="w"&gt; &lt;/span&gt;Unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;element&lt;span class="w"&gt; &lt;/span&gt;by:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;data-testid&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-name&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

Ignored&lt;span class="w"&gt; &lt;/span&gt;nodes:&lt;span class="w"&gt; &lt;/span&gt;comments,&lt;span class="w"&gt; &lt;/span&gt;script,&lt;span class="w"&gt; &lt;/span&gt;style
&amp;lt;html
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;
&amp;gt;
&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;head&amp;gt;



&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;link
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/favicon.ico&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;icon&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;width=device-width, initial-scale=1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;viewport&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#000000&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;theme-color&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Web site created using create-react-app&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;link
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/logo192.png&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;apple-touch-icon&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;




&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;link
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/manifest.json&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;manifest&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;




&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;title&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;React&lt;span class="w"&gt; &lt;/span&gt;App
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/title&amp;gt;


&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;/head&amp;gt;


&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;body&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;noscript&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;You&lt;span class="w"&gt; &lt;/span&gt;need&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;JavaScript&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;app.
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/noscript&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;div
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;root&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;lt;div
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;header
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App-header&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;img
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;logo&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App-logo&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;/&amp;gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;p&amp;gt;
&lt;span class="w"&gt;            &lt;/span&gt;Edit&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&amp;lt;code&amp;gt;
&lt;span class="w"&gt;              &lt;/span&gt;src/App.js
&lt;span class="w"&gt;            &lt;/span&gt;&amp;lt;/code&amp;gt;
&lt;span class="w"&gt;             &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;save&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;reload.
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;/p&amp;gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;a
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App-link&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://reactjs.org&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;noopener noreferrer&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;_blank&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;            &lt;/span&gt;Learn&lt;span class="w"&gt; &lt;/span&gt;React
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;/a&amp;gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;/header&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;lt;/div&amp;gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/div&amp;gt;






&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;...
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Context.eval&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;webpack:///./cypress/e2e/journey.cy.js:4:5&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;displays&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;opponent&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;fixture:
&lt;span class="w"&gt;     &lt;/span&gt;AssertionError:&lt;span class="w"&gt; &lt;/span&gt;Timed&lt;span class="w"&gt; &lt;/span&gt;out&lt;span class="w"&gt; &lt;/span&gt;retrying&lt;span class="w"&gt; &lt;/span&gt;after&lt;span class="w"&gt; &lt;/span&gt;4000ms:&lt;span class="w"&gt; &lt;/span&gt;Unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;element&lt;span class="w"&gt; &lt;/span&gt;by:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;data-testid&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-name&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

Ignored&lt;span class="w"&gt; &lt;/span&gt;nodes:&lt;span class="w"&gt; &lt;/span&gt;comments,&lt;span class="w"&gt; &lt;/span&gt;script,&lt;span class="w"&gt; &lt;/span&gt;style
&amp;lt;html
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;
&amp;gt;
&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;head&amp;gt;



&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;link
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/favicon.ico&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;icon&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;width=device-width, initial-scale=1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;viewport&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#000000&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;theme-color&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Web site created using create-react-app&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;link
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/logo192.png&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;apple-touch-icon&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;




&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;link
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/manifest.json&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;manifest&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;




&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;title&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;React&lt;span class="w"&gt; &lt;/span&gt;App
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/title&amp;gt;


&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;/head&amp;gt;


&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;body&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;noscript&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;You&lt;span class="w"&gt; &lt;/span&gt;need&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;JavaScript&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;app.
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/noscript&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;div
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;root&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;lt;div
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;header
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App-header&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;img
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;logo&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App-logo&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;/&amp;gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;p&amp;gt;
&lt;span class="w"&gt;            &lt;/span&gt;Edit&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&amp;lt;code&amp;gt;
&lt;span class="w"&gt;              &lt;/span&gt;src/App.js
&lt;span class="w"&gt;            &lt;/span&gt;&amp;lt;/code&amp;gt;
&lt;span class="w"&gt;             &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;save&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;reload.
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;/p&amp;gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;a
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App-link&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://reactjs.org&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;noopener noreferrer&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;_blank&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;            &lt;/span&gt;Learn&lt;span class="w"&gt; &lt;/span&gt;React
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;/a&amp;gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;/header&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;lt;/div&amp;gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/div&amp;gt;






&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;...
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Context.eval&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;webpack:///./cypress/e2e/journey.cy.js:17:35&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;displays&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;appropriate&lt;span class="w"&gt; &lt;/span&gt;winner:
&lt;span class="w"&gt;     &lt;/span&gt;AssertionError:&lt;span class="w"&gt; &lt;/span&gt;Timed&lt;span class="w"&gt; &lt;/span&gt;out&lt;span class="w"&gt; &lt;/span&gt;retrying&lt;span class="w"&gt; &lt;/span&gt;after&lt;span class="w"&gt; &lt;/span&gt;4000ms:&lt;span class="w"&gt; &lt;/span&gt;Unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;label&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;text&lt;span class="w"&gt; &lt;/span&gt;of:&lt;span class="w"&gt; &lt;/span&gt;Left

Ignored&lt;span class="w"&gt; &lt;/span&gt;nodes:&lt;span class="w"&gt; &lt;/span&gt;comments,&lt;span class="w"&gt; &lt;/span&gt;script,&lt;span class="w"&gt; &lt;/span&gt;style
&amp;lt;html
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;
&amp;gt;
&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;head&amp;gt;



&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;link
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/favicon.ico&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;icon&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;width=device-width, initial-scale=1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;viewport&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#000000&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;theme-color&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Web site created using create-react-app&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;link
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/logo192.png&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;apple-touch-icon&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;




&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;link
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/manifest.json&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;manifest&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;




&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;title&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;React&lt;span class="w"&gt; &lt;/span&gt;App
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/title&amp;gt;


&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;/head&amp;gt;


&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;body&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;noscript&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;You&lt;span class="w"&gt; &lt;/span&gt;need&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;JavaScript&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;app.
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/noscript&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;div
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;root&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;lt;div
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;header
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App-header&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;img
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;logo&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App-logo&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;/&amp;gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;p&amp;gt;
&lt;span class="w"&gt;            &lt;/span&gt;Edit&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&amp;lt;code&amp;gt;
&lt;span class="w"&gt;              &lt;/span&gt;src/App.js
&lt;span class="w"&gt;            &lt;/span&gt;&amp;lt;/code&amp;gt;
&lt;span class="w"&gt;             &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;save&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;reload.
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;/p&amp;gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;a
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App-link&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://reactjs.org&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;noopener noreferrer&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;_blank&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;            &lt;/span&gt;Learn&lt;span class="w"&gt; &lt;/span&gt;React
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;/a&amp;gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;/header&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;lt;/div&amp;gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/div&amp;gt;






&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;...
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Context.eval&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;webpack:///./cypress/e2e/journey.cy.js:25:5&lt;span class="o"&gt;)&lt;/span&gt;




&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Results&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tests:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Passing:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Failing:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Pending:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Skipped:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Screenshots:&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Video:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;                                                                            &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Duration:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;seconds&lt;span class="w"&gt;                                                                       &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Spec&lt;span class="w"&gt; &lt;/span&gt;Ran:&lt;span class="w"&gt;     &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                                                                    &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘


&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Screenshots&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt;  &lt;/span&gt;path/to/rps-api/cypress/screenshots/journey.cy.js/displays&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;opponent&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1280x720&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;API&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;failed&lt;span class="o"&gt;)&lt;/span&gt;.png&lt;span class="w"&gt;                                              &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt;  &lt;/span&gt;path/to/rps-api/cypress/screenshots/journey.cy.js/displays&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;opponent&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1280x720&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;xture&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;failed&lt;span class="o"&gt;)&lt;/span&gt;.png&lt;span class="w"&gt;                                            &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt;  &lt;/span&gt;path/to/rps-api/cypress/screenshots/journey.cy.js/displays&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;appropriate&lt;span class="w"&gt; &lt;/span&gt;winne&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1280x720&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;r&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;failed&lt;span class="o"&gt;)&lt;/span&gt;.png&lt;span class="w"&gt;                                                &lt;/span&gt;


&lt;span class="o"&gt;====================================================================================&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;Finished&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="w"&gt;       &lt;/span&gt;Spec&lt;span class="w"&gt;                                              &lt;/span&gt;Tests&lt;span class="w"&gt;  &lt;/span&gt;Passing&lt;span class="w"&gt;  &lt;/span&gt;Failing&lt;span class="w"&gt;  &lt;/span&gt;Pending&lt;span class="w"&gt;  &lt;/span&gt;Skipped&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;✖&lt;span class="w"&gt;  &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;:12&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘
&lt;span class="w"&gt;    &lt;/span&gt;✖&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;:12&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;They all fail for uninteresting reasons at the moment (can't find elements by the test ID or label), but it gives us something to work towards. Make a commit, and we'll move down to the React level.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;cypress/e2e/journey.cy.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;cypress/fixtures/example.json

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Failing end-to-end-tests&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;37fd8f7&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Failing&lt;span class="w"&gt; &lt;/span&gt;end-to-end-tests
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;38&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cypress/fixtures/example.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Dealing with APIs [3/8]&lt;/h2&gt;
&lt;p&gt;As we move to the source code, let's think about architecture. Our previous RPS app had the following structure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rpsService&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Outcome&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We're extending this &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="kc"&gt;...&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;randomUserService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rpsService&lt;/span&gt;
&lt;span class="w"&gt;                                  &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Opponent&lt;/span&gt;
&lt;span class="w"&gt;                                 &lt;/span&gt;&lt;span class="n"&gt;Outcome&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Alone with the coordinating &lt;code&gt;App&lt;/code&gt; component, that now gives us two services, two presentation components and one interaction component.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When the app loads, the &lt;code&gt;App&lt;/code&gt; calls the &lt;code&gt;randomUserService&lt;/code&gt; to get an opponent &lt;/li&gt;
&lt;li&gt;The &lt;code&gt;App&lt;/code&gt; passes that to &lt;code&gt;Opponent&lt;/code&gt; for  display&lt;/li&gt;
&lt;li&gt;When the user makes a choice and clicks the Throw button, their choice is sent from the &lt;code&gt;Form&lt;/code&gt; to the &lt;code&gt;App&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;App&lt;/code&gt; calls the &lt;code&gt;rpsService&lt;/code&gt; to get a random weapon for the opponent&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;App&lt;/code&gt; calls the &lt;code&gt;rpsService&lt;/code&gt; again to get the outcome given the player's input and the opponent's weapon&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;App&lt;/code&gt; passes that outcome to &lt;code&gt;Outcome&lt;/code&gt; for display&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;...&lt;/code&gt; on the left represents a request going over the network - the API itself is &lt;em&gt;outside&lt;/em&gt; our system. These are relatively slow, so we don't want them happening for real in our integration or lower level tests. They also introduce variability that makes tests less reliable; your code can be working fine, but a test fails because your wifi is flaky.&lt;/p&gt;
&lt;p&gt;Take a typical request from a single-page app:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;someUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* use data */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You might think we should simply replace &lt;code&gt;fetch&lt;/code&gt; with a test double. The rule of thumb is &lt;em&gt;"don't mock what you don't own"&lt;/em&gt;, though, and here are a few reasons why:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If we replace &lt;code&gt;fetch&lt;/code&gt; we couple our code and tests too closely to that interface. Say we decided to use &lt;a href="https://www.npmjs.com/package/axios"&gt;&lt;code&gt;axios&lt;/code&gt;&lt;/a&gt; instead of &lt;code&gt;fetch&lt;/code&gt;; our existing tests wouldn't help us, we'd have to rewrite all of them. It's hard to be confident that eveything's still correct when you've changed the implementation &lt;em&gt;and&lt;/em&gt; the tests.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Worse still, if the &lt;code&gt;fetch&lt;/code&gt; API changed, our tests would continue to pass even though the implementation doesn't actually work with the new interface. Because we're testing the implementation against the test double, everything seems fine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You could even be wrong about the interface you're replacing and write tests that drive the &lt;em&gt;wrong&lt;/em&gt; impementation. For example check out &lt;a href="https://stackoverflow.com/a/65627662/3001761"&gt;this Stack Overflow answer&lt;/a&gt;, where I found out that the questioner's test double didn't actually match the interface it was replacing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;fetch&lt;/code&gt; specifically is a relatively complicated interface; our test double needs to return a promise of an object with a &lt;code&gt;json&lt;/code&gt; method that returns a promise of the response data:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fetchTestDouble&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;mockResolvedValue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;mockResolvedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Our test double will only gets more complex as our code begins to use other parts of the &lt;code&gt;fetch&lt;/code&gt; API (e.g. checking &lt;code&gt;headers&lt;/code&gt; or looking the &lt;code&gt;status&lt;/code&gt; of the response).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead we're going to use &lt;a href="https://mswjs.io/"&gt;Mock Service Worker&lt;/a&gt; (MSW) to test that the appropriate &lt;em&gt;request&lt;/em&gt; gets made, irrespective of how that's done. This allows us to test the &lt;em&gt;behaviour&lt;/em&gt; (we make a GET request to the random user API) rather than &lt;em&gt;implementation&lt;/em&gt; (we call &lt;code&gt;fetch&lt;/code&gt; with the correct URL). Let's add MSW to our app, following along with &lt;a href="https://v1.mswjs.io/docs/getting-started/"&gt;their instructions&lt;/a&gt;, and write an integration test. Start by installing the package:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;i&lt;span class="w"&gt; &lt;/span&gt;msw@1

added&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;66&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages,&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;audited&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1680&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;8s

&lt;span class="m"&gt;258&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;looking&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;funding
&lt;span class="w"&gt;  &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;fund&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details

&lt;span class="m"&gt;74&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vulnerabilities&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;moderate,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;high&lt;span class="o"&gt;)&lt;/span&gt;

To&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;issues&lt;span class="w"&gt; &lt;/span&gt;that&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;require&lt;span class="w"&gt; &lt;/span&gt;attention,&lt;span class="w"&gt; &lt;/span&gt;run:
&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;audit&lt;span class="w"&gt; &lt;/span&gt;fix

To&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;issues&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;including&lt;span class="w"&gt; &lt;/span&gt;breaking&lt;span class="w"&gt; &lt;/span&gt;changes&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;run:
&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;audit&lt;span class="w"&gt; &lt;/span&gt;fix&lt;span class="w"&gt; &lt;/span&gt;--force

Run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;audit&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: MSW changed substantially between v1 and v2 and getting the latter to work in Jest and with CRA adds a lot of complexity, so for now we'll explicitly continue to use v1.&lt;/p&gt;
&lt;p&gt;As this is a relatively simple configuration, rather than splitting it across multiple files, put the following in &lt;code&gt;src/setupTests.js&lt;/code&gt; (beneath the existing &lt;code&gt;@testing-library&lt;/code&gt; import) to reuse the fixture from our Cypress tests with MSW:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;msw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setupServer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;msw/node&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;../cypress/fixtures/example.json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setupServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://api.randomuser.me&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nx"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetHandlers&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nx"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Our fixture data will be used to respond to a GET request to the API in all of our Jest tests, and everything will be reset between the tests. In the test itself we need to check that the data is appropriately displayed, replacing the content of &lt;code&gt;src/App.test.js&lt;/code&gt; with the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@testing-library/react&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./App&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;displays the opponent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;App&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Randy Wheeler&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-avatar&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toHaveAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;src&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://randomuser.me/api/portraits/thumb/men/12.jpg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; we're using two different query methods here, first awaiting the asynchronous &lt;code&gt;findByTestId&lt;/code&gt; because it will allow the testing library to wait (up to 1,000ms, by default) for the element to appear, then using the synchronous &lt;code&gt;getByTestId&lt;/code&gt; once we know the elements are rendered. Making a request takes &lt;em&gt;time&lt;/em&gt;, and we don't want our whole web app to stop working while we wait for it, so we do it &lt;em&gt;asynchronously&lt;/em&gt; in the background. That makes testing trickier, we need to &lt;em&gt;wait&lt;/em&gt; for the output we want to be available (Cypress is also doing this, but it does it in the background for you).&lt;/p&gt;
&lt;p&gt;Call the shot, and run the tests:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-api@0.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;react-scripts&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;

FAIL&lt;span class="w"&gt; &lt;/span&gt;src/App.test.js
&lt;span class="w"&gt;  &lt;/span&gt;App
&lt;span class="w"&gt;    &lt;/span&gt;✕&lt;span class="w"&gt; &lt;/span&gt;displays&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;opponent&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1036&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;App&lt;span class="w"&gt; &lt;/span&gt;›&lt;span class="w"&gt; &lt;/span&gt;displays&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;opponent

&lt;span class="w"&gt;    &lt;/span&gt;Unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;element&lt;span class="w"&gt; &lt;/span&gt;by:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;data-testid&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;body&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;lt;div&amp;gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;div
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;header
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App-header&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;            &lt;/span&gt;&amp;lt;img
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nv"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;logo&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App-logo&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;logo.svg&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;/&amp;gt;
&lt;span class="w"&gt;            &lt;/span&gt;&amp;lt;p&amp;gt;
&lt;span class="w"&gt;              &lt;/span&gt;Edit&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&amp;lt;code&amp;gt;
&lt;span class="w"&gt;                &lt;/span&gt;src/App.js
&lt;span class="w"&gt;              &lt;/span&gt;&amp;lt;/code&amp;gt;
&lt;span class="w"&gt;               &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;save&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;reload.
&lt;span class="w"&gt;            &lt;/span&gt;&amp;lt;/p&amp;gt;
&lt;span class="w"&gt;            &lt;/span&gt;&amp;lt;a
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App-link&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://reactjs.org&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;noopener noreferrer&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;_blank&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;              &lt;/span&gt;Learn&lt;span class="w"&gt; &lt;/span&gt;React
&lt;span class="w"&gt;            &lt;/span&gt;&amp;lt;/a&amp;gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;/header&amp;gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;/div&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;lt;/div&amp;gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/body&amp;gt;

&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;render&lt;span class="o"&gt;(&lt;/span&gt;&amp;lt;App&lt;span class="w"&gt; &lt;/span&gt;/&amp;gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;await&lt;span class="w"&gt; &lt;/span&gt;screen.findByTestId&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;expect&lt;span class="o"&gt;(&lt;/span&gt;screen.getByTestId&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-name&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;.toHaveTextContent&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Randy Wheeler&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;expect&lt;span class="o"&gt;(&lt;/span&gt;screen.getByTestId&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-avatar&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;waitForWrapper&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/@testing-library/dom/dist/wait-for.js:173:27&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;findByTestId&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node_modules/@testing-library/dom/dist/query-helpers.js:101:33&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;src/App.test.js:9:18&lt;span class="o"&gt;)&lt;/span&gt;

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.134&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you hopefully predicted, the test failed (after just over one second) because the required element never appeared in the default CRA page content. Make a commit to save your work so far:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;package-lock.json
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;package.json
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;src/App.test.js
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;src/setupTests.js

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Failing integration test for opponent&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;a46387e&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Failing&lt;span class="w"&gt; &lt;/span&gt;integration&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;opponent
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1252&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;41&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Opposing forces [4/8]&lt;/h2&gt;
&lt;p&gt;From the diagram above you can see that &lt;code&gt;App&lt;/code&gt; will use two things to do this: a component and a service.  We can do those in either order, so let's start by writing a unit test for the presentation component that will show the opponent; put the following into &lt;code&gt;src/Opponent.test.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;Opp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot and run the test (note that you'll have &lt;em&gt;two&lt;/em&gt; failing tests in Jest, you can use e.g. &lt;code&gt;npm test Opponent&lt;/code&gt; to run only the one above). Initially it will fail because Jest &lt;code&gt;Cannot find module './Opponent' from 'src/Opponent.test.js'&lt;/code&gt; - that should make sense, we haven't created that file yet. Go through the process of making small changes and re-running the test until it passes, with the &lt;em&gt;simplest possible implementation&lt;/em&gt;. Remember to call the shot before each test run, and aim to change the error you get step by step rather than just jumping to the passing test. Once that component works, make a commit to save your work:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/Opponent.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/Opponent.test.js


$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Implement Opponent component&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;46a5dfa&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Implement&lt;span class="w"&gt; &lt;/span&gt;Opponent&lt;span class="w"&gt; &lt;/span&gt;component
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/Opponent.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/Opponent.test.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we need a service. We &lt;em&gt;could&lt;/em&gt; write another integration-style test, using the fixture data from MSW, for example. That seems a bit repetitive, as we're already using MSW at the integration level, so this is a good opportunity to introduce another testing technique for these unit tests. As mentioned above we don't want to mock an interface we don't own, but we could introduce a new interface that we &lt;em&gt;do&lt;/em&gt; own and mock that. This is often referred to as a &lt;em&gt;"facade"&lt;/em&gt; because (like &lt;a href="https://en.wikipedia.org/wiki/Leinster_Gardens#False_houses"&gt;these false buildings&lt;/a&gt; hiding part of the London Underground) it's a thin layer that's easy to fake. For example, you could create this simple function in &lt;code&gt;src/api.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fetchJson&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can then mock out this facade (an interface we own) and test the service against it by creating the following tests in &lt;code&gt;src/randomUserService.test.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fetchJson&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./api&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getRandomUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./randomUserService&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./api&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;random user service&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;requests data from the random user API&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fetchJson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockResolvedValue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getRandomUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetchJson&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://api.randomuser.me&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;extracts the user data from the response body&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Jane&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Doe&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fetchJson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockResolvedValue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getRandomUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You might wonder how we make sure that facade works without mocking &lt;code&gt;fetch&lt;/code&gt;. The answer is that &lt;em&gt;we don't need to&lt;/em&gt; - facades are deliberately very simple, and the code in them is tested at higher levels (in this case by our MSW integration tests and Cypress E2E tests). Run the test (again you can use &lt;code&gt;npm test randomUser&lt;/code&gt; to focus down to this case) and drive out a implementation. Once it's working, make a commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/api.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/randomUserService.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/randomUserService.test.js


$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Implement random user service&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;52d9f2e&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Implement&lt;span class="w"&gt; &lt;/span&gt;random&lt;span class="w"&gt; &lt;/span&gt;user&lt;span class="w"&gt; &lt;/span&gt;service
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/api.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/randomUserService.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/randomUserService.test.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At this point you should have three passing unit tests and one failing integration test. Fix the implementation in the coordinating &lt;code&gt;App&lt;/code&gt; component to call &lt;code&gt;getRandomUser&lt;/code&gt; when the component loads and render an &lt;code&gt;Opponent&lt;/code&gt; presentation component once the data is available. Once all of the tests are passing, return to the E2E tests. We had three Cypress test cases, so call all of the shots then run them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;e2e

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-api@0.1.0&lt;span class="w"&gt; &lt;/span&gt;e2e
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;cypress&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;


&lt;span class="o"&gt;====================================================================================&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;Starting&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Cypress:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;.16.0&lt;span class="w"&gt;                                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Browser:&lt;span class="w"&gt;        &lt;/span&gt;Electron&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;106&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;headless&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Node&lt;span class="w"&gt; &lt;/span&gt;Version:&lt;span class="w"&gt;   &lt;/span&gt;v16.20.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/node&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Specs:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;journey.cy.js&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Searched:&lt;span class="w"&gt;       &lt;/span&gt;cypress/e2e/**/*.cy.&lt;span class="o"&gt;{&lt;/span&gt;js,jsx,ts,tsx&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt;                                            &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────────────────────────────────

&lt;span class="w"&gt;  &lt;/span&gt;Running:&lt;span class="w"&gt;  &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                                                                   &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="w"&gt;  &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;displays&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;opponent&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;API&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;432ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;displays&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;opponent&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;fixture&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;76ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;displays&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;appropriate&lt;span class="w"&gt; &lt;/span&gt;winner

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passing&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;5s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failing

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;displays&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;appropriate&lt;span class="w"&gt; &lt;/span&gt;winner:
&lt;span class="w"&gt;     &lt;/span&gt;AssertionError:&lt;span class="w"&gt; &lt;/span&gt;Timed&lt;span class="w"&gt; &lt;/span&gt;out&lt;span class="w"&gt; &lt;/span&gt;retrying&lt;span class="w"&gt; &lt;/span&gt;after&lt;span class="w"&gt; &lt;/span&gt;4000ms:&lt;span class="w"&gt; &lt;/span&gt;Unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;label&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;text&lt;span class="w"&gt; &lt;/span&gt;of:&lt;span class="w"&gt; &lt;/span&gt;Left

Ignored&lt;span class="w"&gt; &lt;/span&gt;nodes:&lt;span class="w"&gt; &lt;/span&gt;comments,&lt;span class="w"&gt; &lt;/span&gt;script,&lt;span class="w"&gt; &lt;/span&gt;style
&amp;lt;html
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;
&amp;gt;
&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;head&amp;gt;



&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;link
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/favicon.ico&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;icon&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;width=device-width, initial-scale=1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;viewport&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#000000&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;theme-color&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Web site created using create-react-app&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;link
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/logo192.png&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;apple-touch-icon&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;




&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;link
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/manifest.json&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;manifest&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;




&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;title&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;React&lt;span class="w"&gt; &lt;/span&gt;App
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/title&amp;gt;


&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;/head&amp;gt;


&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;body&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;noscript&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;You&lt;span class="w"&gt; &lt;/span&gt;need&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;JavaScript&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;app.
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/noscript&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;div
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;root&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;lt;div
&lt;span class="w"&gt;        &lt;/span&gt;data-testid&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;span
&lt;span class="w"&gt;          &lt;/span&gt;data-testid&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-name&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;          &lt;/span&gt;Liselotte

&lt;span class="w"&gt;          &lt;/span&gt;Welte
&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;/span&amp;gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;img
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nv"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;data-testid&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-avatar&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://randomuser.me/api/portraits/thumb/women/46.jpg&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;/&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;lt;/div&amp;gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/div&amp;gt;






&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;...
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Context.eval&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;webpack:///./cypress/e2e/journey.cy.js:25:5&lt;span class="o"&gt;)&lt;/span&gt;




&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Results&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tests:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Passing:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Failing:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Pending:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Skipped:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Screenshots:&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Video:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;                                                                            &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Duration:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;seconds&lt;span class="w"&gt;                                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Spec&lt;span class="w"&gt; &lt;/span&gt;Ran:&lt;span class="w"&gt;     &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                                                                    &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘


&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Screenshots&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt;  &lt;/span&gt;path/to/rps-api/cypress/screenshots/journey.cy.js/displays&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;appropriate&lt;span class="w"&gt; &lt;/span&gt;winne&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1280x720&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;r&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;failed&lt;span class="o"&gt;)&lt;/span&gt;.png&lt;span class="w"&gt;                                                &lt;/span&gt;


&lt;span class="o"&gt;====================================================================================&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;Finished&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="w"&gt;       &lt;/span&gt;Spec&lt;span class="w"&gt;                                              &lt;/span&gt;Tests&lt;span class="w"&gt;  &lt;/span&gt;Passing&lt;span class="w"&gt;  &lt;/span&gt;Failing&lt;span class="w"&gt;  &lt;/span&gt;Pending&lt;span class="w"&gt;  &lt;/span&gt;Skipped&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;✖&lt;span class="w"&gt;  &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;:04&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘
&lt;span class="w"&gt;    &lt;/span&gt;✖&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;:04&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Hopefully both of the opponent display test cases (&lt;em&gt;"displays a random opponent from the API"&lt;/em&gt; and &lt;em&gt;"displays an opponent from a fixture"&lt;/em&gt;) are passing, so even though the third (&lt;em&gt;"displays the appropriate winner"&lt;/em&gt;) is failing, let's make a commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;src/App.js


$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Implement computer opponent display&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;037e7ab&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Implement&lt;span class="w"&gt; &lt;/span&gt;computer&lt;span class="w"&gt; &lt;/span&gt;opponent&lt;span class="w"&gt; &lt;/span&gt;display
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;rewrite&lt;span class="w"&gt; &lt;/span&gt;src/App.js&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;90&lt;/span&gt;%&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;On form [5/8]&lt;/h2&gt;
&lt;p&gt;Let's bring in the actual game playing. From the &lt;code&gt;App&lt;/code&gt; component's perspective, we can write an integration test very similar to the one we have at the E2E level; add the following to &lt;code&gt;App.test.js&lt;/code&gt; (you'll also need to &lt;code&gt;import userEvent from "@testing-library/user-event";&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;lets you play the random opponent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;playerWeapon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;outcomes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Right wins!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Left wins!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Draw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;App&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;playerWeapon&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Throw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-weapon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opponentWeapon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-weapon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;outcome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outcomes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;opponentWeapon&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I've deliberately chosen a different weapon and set of outcomes to the E2E test - if we have the same test case over and over again at different levels we might miss some of the cases or hard-code something accidentally. We'll use this test to guide the next few steps, slowly moving the point of failure further through the test, until it's passing. Call the shot and run the test; hopefully it's failing for a sensible reason. If so, commit it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;src/App.test.js


$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Failing integration test for playing opponent&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;960d7c6&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Failing&lt;span class="w"&gt; &lt;/span&gt;integration&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;playing&lt;span class="w"&gt; &lt;/span&gt;opponent
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Our form is a bit simpler than last time, as we only have one input. Add the following to &lt;code&gt;src/Form.test.js&lt;/code&gt; and use it to drive out the implementation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@testing-library/react&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@testing-library/user-event&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./Form&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Form component&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;emits the user&amp;#39;s input&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;weapon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;weapon&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Throw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weapon&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once it's passing, run the &lt;code&gt;App&lt;/code&gt; integration tests too - you should be able to change the current failure for that test by adding in the &lt;code&gt;Form&lt;/code&gt; component (note you'll probably want to set &lt;code&gt;onSubmit={() =&amp;gt; {}}&lt;/code&gt; in &lt;code&gt;App&lt;/code&gt; to prevent &lt;code&gt;TypeError: onSubmit is not a function&lt;/code&gt;). Call the shot, run the test. Once the failure's coming later in the test, make a commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;src/App.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/Form.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/Form.test.js


$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Implement Form component&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;bb3f4d6&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Implement&lt;span class="w"&gt; &lt;/span&gt;Form&lt;span class="w"&gt; &lt;/span&gt;component
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/Form.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/Form.test.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;A roll of the dice [6/8]&lt;/h2&gt;
&lt;p&gt;Now we can implement the RPS service. We already have most of it from previous implementations, so copy the existing &lt;code&gt;rpsService.js&lt;/code&gt; and &lt;code&gt;rpsService.test.js&lt;/code&gt; into &lt;code&gt;src/&lt;/code&gt; and commit it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-e2e/src/rpsService*.js&lt;span class="w"&gt; &lt;/span&gt;./src

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/rpsService.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/rpsService.test.js


$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Implement RPS service&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;498c60f&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Implement&lt;span class="w"&gt; &lt;/span&gt;RPS&lt;span class="w"&gt; &lt;/span&gt;service
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;75&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/rpsService.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/rpsService.test.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we're going to add some new functionality, a function that gives us a random weapon. So we can think about how to test it, here's a basic implementation of a coin flip:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;flipCoin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;heads&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tails&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can be pretty confident that if we call this function a large number of times we'll get roughly half of each outcome:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;flipCoin&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tails&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;heads&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;heads&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tails&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tails&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tails&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;heads&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;heads&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tails&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tails&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;but for a given call we can't be sure which it will be. So how can we write a test for that? We could reach for the facade pattern again, extract &lt;code&gt;const random = () =&amp;gt; Math.random()&lt;/code&gt; and replace that with a test double. This would work fine, but be very tightly coupled to the implementation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;flipCoin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;returns &amp;#39;heads&amp;#39; when the random number is less than 0.5&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockReturnValue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flipCoin&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;heads&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;One alternative is to write tests based on the &lt;em&gt;properties&lt;/em&gt; of the implementation we want. For example, although we don't know the specific values, we do know:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It should always give one of the expected outcomes; and&lt;/li&gt;
&lt;li&gt;It shouldn't always give the same outcome (otherwise &lt;code&gt;() =&amp;gt; "heads"&lt;/code&gt; would be a valid implementation).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can express these properties through a pair of tests as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;flipCoin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;expectedOutcomes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;heads&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tails&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;always gives one of the expected outcomes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;outcomes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;flipCoin&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outcomes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;expectedOutcomes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;))).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;doesn&amp;#39;t always give the same outcome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;outcomes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;flipCoin&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedOutcomes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;outcomes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;))).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is less coupled because it describes the &lt;strong&gt;what&lt;/strong&gt;, the &lt;em&gt;behaviour&lt;/em&gt; we want from the function, not the &lt;strong&gt;how&lt;/strong&gt;, the details of the implementation. That allows us to refactor with confidence if we think of a better way to do it later.&lt;/p&gt;
&lt;p&gt;We could also try to test for the property that it's roughly 50:50 &lt;code&gt;"heads"&lt;/code&gt; to &lt;code&gt;"tails"&lt;/code&gt;, but then questions like &lt;em&gt;"what do you mean by 'roughly'?"&lt;/em&gt; come up. If you try to be too specific, the tests will often fail due to the randomness in the real data. Note that the second test above could already occasionally fail; it's not impossible to get 100 heads in a row, just extremely unlikely.&lt;/p&gt;
&lt;p&gt;Write two tests for a &lt;code&gt;randomWeapon&lt;/code&gt; function, to ensure it always returns one of the three expected weapons &lt;code&gt;"rock"&lt;/code&gt;, &lt;code&gt;"paper"&lt;/code&gt; or &lt;code&gt;"scissors"&lt;/code&gt; but doesn't always return the same weapon, then write an implementation that passes those tests by returning a random weapon. Once that's working, make a commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;src/rpsService.js
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;src/rpsService.test.js


$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Implement random weapon function&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2937240&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Implement&lt;span class="w"&gt; &lt;/span&gt;random&lt;span class="w"&gt; &lt;/span&gt;weapon&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletion&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;A good outcome [7/8]&lt;/h2&gt;
&lt;p&gt;Now we need to display this random weapon for the opponent, but only once it's been selected, so add two new tests to &lt;code&gt;Opponent.test.js&lt;/code&gt; as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;doesn&amp;#39;t show the opponent&amp;#39;s weapon initially&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Opponent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opponent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;picture&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-weapon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shows the opponent&amp;#39;s weapon once selected&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;weapon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Opponent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;weapon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;weapon&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opponent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;picture&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opponent-weapon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weapon&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once these tests are passing, update the &lt;code&gt;App&lt;/code&gt; component to move the failure of its integration test along again.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;src/App.js
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;src/Opponent.js
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;src/Opponent.test.js


$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Add weapon to the Opponent component&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;e037a5f&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Add&lt;span class="w"&gt; &lt;/span&gt;weapon&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;Opponent&lt;span class="w"&gt; &lt;/span&gt;component
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Outcome&lt;/code&gt; component is exactly the same as last time, so let's copy that in too:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-e2e/src/Outcome*.js&lt;span class="w"&gt; &lt;/span&gt;./src
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is already tested and working, so wire it into the &lt;code&gt;App&lt;/code&gt; component to complete the implementation. Check that it's working correctly by running all of the Jest unit/integration tests then the Cypress E2E tests. Congratulations, you're done, make a final commit!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;src/App.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/Outcome.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/Outcome.test.js


$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Show the outcome&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;71f7eab&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Show&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;outcome
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;39&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/Outcome.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/Outcome.test.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now reflect on the exercise - how does the implementation compare to what you'd initially imagined? What felt good or bad about the process?&lt;/p&gt;
&lt;p&gt;You can see my copy of this exercise at &lt;a href="https://github.com/textbook/rps-api"&gt;https://github.com/textbook/rps-api&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Exercises [8/8]&lt;/h2&gt;
&lt;p&gt;Here are some additional exercises you can run through:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;What should happen if a request to the random user API fails? How can you test for that, and at what levels (using Cypress E2E, MSW integration and/or unit tests)? Note that the &lt;code&gt;fetch&lt;/code&gt; facade should hide the details of the transport layer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fetchJson&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="cm"&gt;/* Throw error? Return default values? */&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Refactor one or more of the components from function-based to class-based, or vice versa. You shouldn't need to change any of the tests!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Try to repeat the whole exercise &lt;em&gt;without&lt;/em&gt; integration or end-to-end tests, just driving out the code with unit tests and putting them together yourself. Does that feel better or worse? Do you make any mistakes higher-level tests would have caught?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Perhaps the &lt;code&gt;Opponent&lt;/code&gt; component should make the request for the user data itself (e.g. a &lt;code&gt;fetch&lt;/code&gt; in a &lt;code&gt;useEffect&lt;/code&gt; hook)? Refactor accordingly - what new tests are needed, which existing tests need to be changed, and which ones get removed entirely?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What should happen when the player changes their input but haven't yet clicked the "Throw" button? Currently the opponent's last throw is still shown, along with an outcome that no longer applies. Test drive out better behaviour.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Refactor the components (&lt;code&gt;Form&lt;/code&gt;, &lt;code&gt;Opponent&lt;/code&gt;, &lt;code&gt;Outcome&lt;/code&gt;- don't include &lt;code&gt;App&lt;/code&gt;) into a new directory &lt;code&gt;src/components/&lt;/code&gt; and the services (&lt;code&gt;randomUserService&lt;/code&gt; plus &lt;code&gt;api&lt;/code&gt;, &lt;code&gt;rpsService&lt;/code&gt;) into another directory &lt;code&gt;src/services/&lt;/code&gt;. Did the tests make this easier? Harder?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find another free API (see e.g. &lt;a href="https://apilist.fun/"&gt;https://apilist.fun/&lt;/a&gt;) and test-drive some kind of UI for it. Include tests that use the real API as well as those that provide test doubles at various levels.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you've implemented additional weapons (e.g. Rock Paper Scissors Lizard Spock) in the previous implementations, extend the UI to support them. If not, maybe now's the time!&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I'd recommend creating a new git branch for each one you try (e.g. use &lt;code&gt;git checkout -b &amp;lt;name&amp;gt;&lt;/code&gt;) and making commits as appropriate.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Once you're ready to move on&lt;/strong&gt;, check out &lt;a href="https://blog.jonrshar.pe/2023/May/23/js-tdd-ohm.html"&gt;the next article&lt;/a&gt; in this series where we'll cover testing the API producer and discuss more about how tests drive design.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="bonus"&gt;Automatic E2E [Bonus]&lt;/h2&gt;
&lt;p&gt;As I mentioned in my &lt;a href="https://blog.jonrshar.pe/2019/Feb/10/automation-for-the-people.html"&gt;article on automation&lt;/a&gt;, I'm a fan of making developers' lives easier through automating the things you do frequently. You may have found it a bit annoying that you had to manually juggle two terminal windows through the last two articles, one where the app's running (&lt;code&gt;npm start&lt;/code&gt;) and another where you run the E2E tests (&lt;code&gt;npm run e2e&lt;/code&gt;). So let's simplify that!&lt;/p&gt;
&lt;p&gt;First, install some useful helper dependencies:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;concurrently&lt;span class="w"&gt; &lt;/span&gt;cross-env&lt;span class="w"&gt; &lt;/span&gt;wait-on
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then add some extra scripts to the &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;quot;e2e:ci&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;concurrently --kill-others --success first \&amp;quot;npm:e2e:ci:*\&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;&amp;quot;e2e:ci:app&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cross-env BROWSER=none PORT=4321 npm start&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;&amp;quot;pree2e:ci:run&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;wait-on --log --timeout 30000 http-get://localhost:4321&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;&amp;quot;e2e:ci:run&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cross-env CYPRESS_BASE_URL=http://localhost:4321 npm run e2e&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So what does that do? When we run &lt;code&gt;npm run e2e:ci&lt;/code&gt;, the &lt;code&gt;concurrently&lt;/code&gt; script is going to run two things in parallel for us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;e2e:ci:app&lt;/code&gt;: Run the app using &lt;code&gt;npm start&lt;/code&gt;, with some environment variables set via &lt;code&gt;cross-env&lt;/code&gt; (this allows it to work on *nix and Windows):&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BROWSER=none&lt;/code&gt; stops the browser from popping up and taking over the screen; and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PORT=4321&lt;/code&gt; runs the app on the specified port (so we can still have a version running on port 3000 without causing any conflicts).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;e2e:ci:run&lt;/code&gt;: Run the E2E tests in a two-step process:&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;pre&lt;/code&gt; script runs first, and uses &lt;code&gt;wait-on&lt;/code&gt; to wait for up to 30,000ms for the app to be running on the specified port; then&lt;/li&gt;
&lt;li&gt;If that works (i.e. the app starts in less than 30s) run the actual tests, with the &lt;code&gt;baseUrl&lt;/code&gt; configuration overridden to point to the right place.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The other configuration options passed to &lt;code&gt;concurrently&lt;/code&gt; itself are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--kill-others&lt;/code&gt;, meaning stop all of the other processes when one exits (in this case we expect our tests to exit at some point and want to stop the app when they do); and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--success first&lt;/code&gt;, meaning that the output of the overall command is the output of the first one to exit (i.e. output from the &lt;code&gt;e2e:ci&lt;/code&gt; command should be the output from the tests).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The overall result will look like the following (&lt;code&gt;concurrently&lt;/code&gt; prefixes the logs from the different processes so you can see what's coming from where):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;e2e:ci

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-api@0.1.0&lt;span class="w"&gt; &lt;/span&gt;e2e:ci
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;concurrently&lt;span class="w"&gt; &lt;/span&gt;--kill-others&lt;span class="w"&gt; &lt;/span&gt;--success&lt;span class="w"&gt; &lt;/span&gt;first&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;npm:e2e:ci:*&amp;quot;&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-api@0.1.0&lt;span class="w"&gt; &lt;/span&gt;e2e:ci:app
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;cross-env&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;BROWSER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;none&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;4321&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;start
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-api@0.1.0&lt;span class="w"&gt; &lt;/span&gt;pree2e:ci:run
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;wait-on&lt;span class="w"&gt; &lt;/span&gt;--log&lt;span class="w"&gt; &lt;/span&gt;--timeout&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;30000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;http-get://localhost:4321
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;waiting&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;resources:&lt;span class="w"&gt; &lt;/span&gt;http-get://localhost:4321
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-api@0.1.0&lt;span class="w"&gt; &lt;/span&gt;start
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;react-scripts&lt;span class="w"&gt; &lt;/span&gt;start
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:74442&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;DEP_WEBPACK_DEV_SERVER_ON_AFTER_SETUP_MIDDLEWARE&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;DeprecationWarning:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;onAfterSetupMiddleware&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;option&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;deprecated.&lt;span class="w"&gt; &lt;/span&gt;Please&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;setupMiddlewares&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;option.
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;--trace-deprecation&lt;span class="w"&gt; &lt;/span&gt;...&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;show&lt;span class="w"&gt; &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;warning&lt;span class="w"&gt; &lt;/span&gt;was&lt;span class="w"&gt; &lt;/span&gt;created&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:74442&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;DEP_WEBPACK_DEV_SERVER_ON_BEFORE_SETUP_MIDDLEWARE&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;DeprecationWarning:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;onBeforeSetupMiddleware&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;option&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;deprecated.&lt;span class="w"&gt; &lt;/span&gt;Please&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;setupMiddlewares&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;option.
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Starting&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;development&lt;span class="w"&gt; &lt;/span&gt;server...
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Compiled&lt;span class="w"&gt; &lt;/span&gt;successfully!
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;You&lt;span class="w"&gt; &lt;/span&gt;can&lt;span class="w"&gt; &lt;/span&gt;now&lt;span class="w"&gt; &lt;/span&gt;view&lt;span class="w"&gt; &lt;/span&gt;rps-api&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;browser.
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;Local:&lt;span class="w"&gt;            &lt;/span&gt;http://localhost:4321
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;On&lt;span class="w"&gt; &lt;/span&gt;Your&lt;span class="w"&gt; &lt;/span&gt;Network:&lt;span class="w"&gt;  &lt;/span&gt;http://192.168.1.235:4321
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Note&lt;span class="w"&gt; &lt;/span&gt;that&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;development&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;optimized.
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;To&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;production&lt;span class="w"&gt; &lt;/span&gt;build,&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;build.
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;webpack&lt;span class="w"&gt; &lt;/span&gt;compiled&lt;span class="w"&gt; &lt;/span&gt;successfully
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;wait-on&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;74439&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;complete&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-api@0.1.0&lt;span class="w"&gt; &lt;/span&gt;e2e:ci:run
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;cross-env&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CYPRESS_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:4321&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;e2e
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-api@0.1.0&lt;span class="w"&gt; &lt;/span&gt;e2e
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;cypress&lt;span class="w"&gt; &lt;/span&gt;run
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;DevTools&lt;span class="w"&gt; &lt;/span&gt;listening&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;ws://127.0.0.1:56736/devtools/browser/d1f8b0f9-39f5-4af0-b8f4-a7dc10ba3b68
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Couldn&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;tsconfig.json.&lt;span class="w"&gt; &lt;/span&gt;tsconfig-paths&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;skipped
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;====================================================================================&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;Starting&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Cypress:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;.16.0&lt;span class="w"&gt;                                                                        &lt;/span&gt;│
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Browser:&lt;span class="w"&gt;        &lt;/span&gt;Electron&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;106&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;headless&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Node&lt;span class="w"&gt; &lt;/span&gt;Version:&lt;span class="w"&gt;   &lt;/span&gt;v16.20.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/node&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Specs:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;journey.cy.js&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Searched:&lt;span class="w"&gt;       &lt;/span&gt;cypress/e2e/**/*.cy.&lt;span class="o"&gt;{&lt;/span&gt;js,jsx,ts,tsx&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt;                                            &lt;/span&gt;│
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;────────────────────────────────────────────────────────────────────────────────────────────────────
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;                                                                                                     &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;Running:&lt;span class="w"&gt;  &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                                                                   &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;displays&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;opponent&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;API&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;413ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;displays&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;opponent&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;fixture&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;81ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;displays&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;appropriate&lt;span class="w"&gt; &lt;/span&gt;winner&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;277ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passing&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;793ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Results&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tests:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Passing:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Failing:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Pending:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Skipped:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Screenshots:&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Video:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;                                                                            &lt;/span&gt;│
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Duration:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;seconds&lt;span class="w"&gt;                                                                        &lt;/span&gt;│
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Spec&lt;span class="w"&gt; &lt;/span&gt;Ran:&lt;span class="w"&gt;     &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                                                                    &lt;/span&gt;│
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;====================================================================================&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;Finished&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;Spec&lt;span class="w"&gt;                                              &lt;/span&gt;Tests&lt;span class="w"&gt;  &lt;/span&gt;Passing&lt;span class="w"&gt;  &lt;/span&gt;Failing&lt;span class="w"&gt;  &lt;/span&gt;Pending&lt;span class="w"&gt;  &lt;/span&gt;Skipped&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;✔&lt;span class="w"&gt;  &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                            &lt;/span&gt;790ms&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;│
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;✔&lt;span class="w"&gt;  &lt;/span&gt;All&lt;span class="w"&gt; &lt;/span&gt;specs&lt;span class="w"&gt; &lt;/span&gt;passed!&lt;span class="w"&gt;                        &lt;/span&gt;790ms&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;run&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;e2e:ci:run&lt;span class="w"&gt; &lt;/span&gt;exited&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;code&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
--&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Sending&lt;span class="w"&gt; &lt;/span&gt;SIGTERM&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;other&lt;span class="w"&gt; &lt;/span&gt;processes..
&lt;span class="o"&gt;[&lt;/span&gt;app&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;e2e:ci:app&lt;span class="w"&gt; &lt;/span&gt;exited&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;code&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can go even further and run the end-to-end tests against the build output, rather than the development server, which lets you check that the app compiles correctly. Run &lt;code&gt;npm install serve&lt;/code&gt; to add a simple web server package, then replace the &lt;code&gt;"e2e:ci:app"&lt;/code&gt; script with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;pree2e:ci:app&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;npm run build&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;e2e:ci:app&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;serve -l 4321 build/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As above the &lt;code&gt;pre&lt;/code&gt; script runs first, to build the app, then if that's successful the main script starts to serve on the appropriate port (&lt;code&gt;-l 4321&lt;/code&gt;) from the output directory (&lt;code&gt;build/&lt;/code&gt;).&lt;/p&gt;</content><category term="development"></category><category term="javascript"></category><category term="tdd"></category><category term="xp"></category></entry><entry><title>JS TDD E2E</title><link href="https://blog.jonrshar.pe/2020/Nov/22/js-tdd-e2e.html" rel="alternate"></link><published>2020-11-22T15:30:00+00:00</published><updated>2023-12-17T11:30:00+00:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2020-11-22:/2020/Nov/22/js-tdd-e2e.html</id><summary type="html">&lt;p&gt;Test-driven JavaScript development done right - part 2&lt;/p&gt;</summary><content type="html">&lt;p&gt;In &lt;a href="https://blog.jonrshar.pe/2020/Aug/31/js-tdd-ftw.html"&gt;the previous article&lt;/a&gt; in this series, I introduced some of the basics of test-driven development (TDD):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;the process:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Red - write a failing test that describes the behaviour you want;&lt;/li&gt;
&lt;li&gt;Green - write the simplest possible code to make the test pass; and&lt;/li&gt;
&lt;li&gt;Refactor - clean up your code without breaking the tests.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the three main parts of a test:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Arrange&lt;/strong&gt; (sometimes known as &lt;em&gt;"given"&lt;/em&gt;) - set up the preconditions for our test...&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Act&lt;/strong&gt; (or &lt;em&gt;"when"&lt;/em&gt;) - do some work... This is what we're actually testing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assert&lt;/strong&gt; (or &lt;em&gt;"then"&lt;/em&gt;) - make sure that the work was done correctly.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;some of the benefits of test-driving implementations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;"...we can try out how we should interact with our code (its "interface") before we've even written any. We can have that discussion... while it's just a matter of changing our minds rather than the code."&lt;/em&gt;;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;"...it tells you when you're done. Once the tests are passing, the implementation meets the current requirements."&lt;/em&gt;; and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;"...we know that the code still does exactly what it's supposed to even [when] we've just changed the implementation. This allows us to confidently refactor towards cleaner code and higher quality."&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;how to &lt;em&gt;"call the shot"&lt;/em&gt; when running your tests:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;...make a prediction of what the test result will be, pass or fail. 
If you think the test will fail, &lt;strong&gt;why&lt;/strong&gt;; will the &lt;code&gt;expect&lt;/code&gt;ation be 
unmet (and what value do you think you'll get instead) or will 
something else go wrong?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also covered how to use one of the most popular test frameworks in the JavaScript ecosystem at the moment, &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt;, to start writing unit tests for a simple function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock, paper, scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should say left wins for rock vs. scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Arrange&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Act&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Assert&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Along the way I threw in some *nix CLI commands and practice with using Git. If you haven't read it yet, or any of the above seems unfamiliar, go and check it out!&lt;/p&gt;
&lt;p&gt;One thing that seems to frustrate people new to TDD is that many of the examples are, like my previous post, pretty trivial. They're useful for teaching the flow, but don't actually show you how to test most real applications. So to address that I thought for round two I'd meet a pretty common need - end-to-end (E2E, sometimes known as acceptance or functional) testing a React web app built with &lt;a href="https://create-react-app.dev/docs/getting-started"&gt;Create React App&lt;/a&gt; (CRA). This will still use the TDD flow, but add an extra layer with some &lt;a href="https://cypress.io"&gt;Cypress&lt;/a&gt; browser tests. We'll work our way from the &lt;em&gt;outside in&lt;/em&gt;, starting with the E2E tests then moving to lower levels.&lt;/p&gt;
&lt;p&gt;Note that I'll use the following names to refer to the different levels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;End-to-end&lt;/strong&gt;: exercises the whole application from the user's point of view;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration&lt;/strong&gt;: exercises multiple components of the application, collaborating to provide some functionality; and&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unit&lt;/strong&gt;: exercises a single component, with any collaborating components replaced by &lt;a href="https://tanzu.vmware.com/content/pivotal-engineering-journal/the-test-double-rule-of-thumb-2"&gt;test doubles&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Requirements&lt;/h3&gt;
&lt;p&gt;The prerequisites here are the same as the previous article:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;*nix command line: already provided on macOS and Linux; if you're using Windows try &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/about"&gt;WSL&lt;/a&gt; or &lt;a href="https://gitforwindows.org/"&gt;Git BASH&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/"&gt;Node&lt;/a&gt; (16+ recommended, Jest 29 &lt;a href="https://jestjs.io/docs/upgrading-to-jest29#compatibility"&gt;dropped support&lt;/a&gt; for Node 12; run &lt;code&gt;node -v&lt;/code&gt; to check) and NPM; and&lt;/li&gt;
&lt;li&gt;Familiarity with ES6 JavaScript syntax.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition, given the domain for this post, you'll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Familiarity with React development - I'm going to assume you know how to write a basic implementation, guiding you with test cases and a few function component examples.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We're going to expand on the previous article and add a web UI for our Rock Paper Scissors implementation. This article moves quite quickly; the libraries involved (&lt;a href="https://cypress.io"&gt;Cypress&lt;/a&gt;, &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt;, &lt;a href="https://testing-library.com"&gt;Testing Library&lt;/a&gt;) have quite large APIs, so it's best to read the details in their documentation.&lt;/p&gt;
&lt;p&gt;Again please carefully &lt;em&gt;read everything&lt;/em&gt;, and for newer developers I'd recommend &lt;em&gt;typing the code&lt;/em&gt; rather than copy-pasting.&lt;/p&gt;
&lt;h2&gt;Setup [1/8]&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: to use Vite, a more up-to-date alternative to CRA, see
&lt;a href="https://blog.jonrshar.pe/2023/Dec/17/js-tdd-vite.html"&gt;supplement A&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To begin, let's create a new React app in our workspace using CRA:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npx&lt;span class="w"&gt; &lt;/span&gt;create-react-app@latest&lt;span class="w"&gt; &lt;/span&gt;rps-e2e

Creating&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;React&lt;span class="w"&gt; &lt;/span&gt;app&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-e2e.

Installing&lt;span class="w"&gt; &lt;/span&gt;packages.&lt;span class="w"&gt; &lt;/span&gt;This&lt;span class="w"&gt; &lt;/span&gt;might&lt;span class="w"&gt; &lt;/span&gt;take&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;couple&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;minutes.
Installing&lt;span class="w"&gt; &lt;/span&gt;react,&lt;span class="w"&gt; &lt;/span&gt;react-dom,&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;react-scripts&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;cra-template...


added&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1427&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;32s

&lt;span class="m"&gt;226&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;looking&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;funding
&lt;span class="w"&gt;  &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;fund&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details

Initialized&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;repository.

Installing&lt;span class="w"&gt; &lt;/span&gt;template&lt;span class="w"&gt; &lt;/span&gt;dependencies&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;npm...

added&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;74&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages,&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;changed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;package&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;3s

&lt;span class="m"&gt;235&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;looking&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;funding
&lt;span class="w"&gt;  &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;fund&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details
Removing&lt;span class="w"&gt; &lt;/span&gt;template&lt;span class="w"&gt; &lt;/span&gt;package&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;npm...


removed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;package,&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;audited&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1501&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;2s

&lt;span class="m"&gt;235&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;looking&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;funding
&lt;span class="w"&gt;  &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;fund&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details

&lt;span class="m"&gt;74&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vulnerabilities&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;moderate,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;high&lt;span class="o"&gt;)&lt;/span&gt;

To&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;issues&lt;span class="w"&gt; &lt;/span&gt;that&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;require&lt;span class="w"&gt; &lt;/span&gt;attention,&lt;span class="w"&gt; &lt;/span&gt;run:
&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;audit&lt;span class="w"&gt; &lt;/span&gt;fix

To&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;issues&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;including&lt;span class="w"&gt; &lt;/span&gt;breaking&lt;span class="w"&gt; &lt;/span&gt;changes&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;run:
&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;audit&lt;span class="w"&gt; &lt;/span&gt;fix&lt;span class="w"&gt; &lt;/span&gt;--force

Run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;audit&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details.

Created&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit.

Success!&lt;span class="w"&gt; &lt;/span&gt;Created&lt;span class="w"&gt; &lt;/span&gt;rps-e2e&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-e2e
Inside&lt;span class="w"&gt; &lt;/span&gt;that&lt;span class="w"&gt; &lt;/span&gt;directory,&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;can&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;several&lt;span class="w"&gt; &lt;/span&gt;commands:

&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;start
&lt;span class="w"&gt;    &lt;/span&gt;Starts&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;development&lt;span class="w"&gt; &lt;/span&gt;server.

&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;build
&lt;span class="w"&gt;    &lt;/span&gt;Bundles&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;app&lt;span class="w"&gt; &lt;/span&gt;into&lt;span class="w"&gt; &lt;/span&gt;static&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;production.

&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;Starts&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;runner.

&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;eject
&lt;span class="w"&gt;    &lt;/span&gt;Removes&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;tool&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;copies&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;dependencies,&lt;span class="w"&gt; &lt;/span&gt;configuration&lt;span class="w"&gt; &lt;/span&gt;files
&lt;span class="w"&gt;    &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;scripts&lt;span class="w"&gt; &lt;/span&gt;into&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;app&lt;span class="w"&gt; &lt;/span&gt;directory.&lt;span class="w"&gt; &lt;/span&gt;If&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;this,&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;can’t&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;back!

We&lt;span class="w"&gt; &lt;/span&gt;suggest&lt;span class="w"&gt; &lt;/span&gt;that&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;begin&lt;span class="w"&gt; &lt;/span&gt;by&lt;span class="w"&gt; &lt;/span&gt;typing:

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rps-e2e
&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;start

Happy&lt;span class="w"&gt; &lt;/span&gt;hacking!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There's a &lt;em&gt;lot&lt;/em&gt; of output here, but you should see four main stages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install &lt;code&gt;create-react-app&lt;/code&gt; using &lt;code&gt;npx&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Install the specified template (we're using the default, &lt;code&gt;cra-template&lt;/code&gt;) and the main React and &lt;code&gt;react-scripts&lt;/code&gt; dependencies;&lt;/li&gt;
&lt;li&gt;Install the dependencies the template defines (mostly &lt;code&gt;@testing-library&lt;/code&gt; utilities in this case); and finally&lt;/li&gt;
&lt;li&gt;Uninstall the template.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This also takes care of the initial steps like creating a directory, a git repository (with &lt;code&gt;node_modules/&lt;/code&gt; already ignored for us) and an NPM package. This time, before getting to the unit test level with Jest (which CRA has already set up for us), let's enter the project directory and install Cypress, for our end-to-end tests, as well as the latest version of Testing Library's packages (CRA installs v13 by default) and &lt;a href="https://testing-library.com/docs/cypress-testing-library/intro/"&gt;their Cypress utilities&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rps-e2e/

$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;cypress&lt;span class="w"&gt; &lt;/span&gt;@testing-library/&lt;span class="o"&gt;{&lt;/span&gt;cypress,react,user-event&lt;span class="o"&gt;}&lt;/span&gt;@latest

added&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;119&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages,&lt;span class="w"&gt; &lt;/span&gt;removed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages,&lt;span class="w"&gt; &lt;/span&gt;changed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages,&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;audited&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1612&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;7s

&lt;span class="m"&gt;251&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;looking&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;funding
&lt;span class="w"&gt;  &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;fund&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details

&lt;span class="m"&gt;74&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vulnerabilities&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;moderate,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;high&lt;span class="o"&gt;)&lt;/span&gt;

To&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;issues&lt;span class="w"&gt; &lt;/span&gt;that&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;require&lt;span class="w"&gt; &lt;/span&gt;attention,&lt;span class="w"&gt; &lt;/span&gt;run:
&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;audit&lt;span class="w"&gt; &lt;/span&gt;fix

To&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;issues&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;including&lt;span class="w"&gt; &lt;/span&gt;breaking&lt;span class="w"&gt; &lt;/span&gt;changes&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;run:
&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;audit&lt;span class="w"&gt; &lt;/span&gt;fix&lt;span class="w"&gt; &lt;/span&gt;--force

Run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;audit&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Don't worry about the vulnerability reports (and definitely &lt;strong&gt;do not&lt;/strong&gt; run &lt;code&gt;npm audit fix --force&lt;/code&gt; without understanding exactly what it does - that can break the dependency tree entirely), more information is available on the CRA issues list &lt;a href="https://github.com/facebook/create-react-app/issues/11174"&gt;here&lt;/a&gt;. Note that CRA installs everything as a regular dependency rather than a development dependency, so I didn't use &lt;code&gt;--save-dev&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Create E2E suite [2/8]&lt;/h2&gt;
&lt;p&gt;Now we need to set up the basic Cypress configuration. To do this, we'll open up the Cypress UI.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;./node_modules/.bin/&lt;/code&gt; is where NPM puts all of the &lt;em&gt;executables&lt;/em&gt; that your installed packages define. For example, if you look in that directory for this project or the previous &lt;code&gt;rps-tdd/&lt;/code&gt; project, you'll see &lt;code&gt;jest&lt;/code&gt; in there; that's what gets called when we &lt;code&gt;npm run test&lt;/code&gt; if the script is &lt;code&gt;"test": "jest"&lt;/code&gt;. Most often you'll be running these via scripts defined in your package file, but you can also run them directly if needed.&lt;/p&gt;
&lt;p&gt;In this case we only need to run &lt;code&gt;cypress open&lt;/code&gt; once, so let's do it like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npx&lt;span class="w"&gt; &lt;/span&gt;cypress&lt;span class="w"&gt; &lt;/span&gt;open
It&lt;span class="w"&gt; &lt;/span&gt;looks&lt;span class="w"&gt; &lt;/span&gt;like&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;your&lt;span class="w"&gt; &lt;/span&gt;first&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;Cypress:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;.16.0

✔&lt;span class="w"&gt;  &lt;/span&gt;Verified&lt;span class="w"&gt; &lt;/span&gt;Cypress!&lt;span class="w"&gt; &lt;/span&gt;path/to/Cypress/12.16.0/Cypress…

Opening&lt;span class="w"&gt; &lt;/span&gt;Cypress...

DevTools&lt;span class="w"&gt; &lt;/span&gt;listening&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;ws://127.0.0.1:50213/devtools/browser/788298a9-c866-4f0a-b212-bcf59b335b60
Couldn&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;tsconfig.json.&lt;span class="w"&gt; &lt;/span&gt;tsconfig-paths&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;skipped
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This should open the Cypress GUI:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the Cypress GUI" src="https://blog.jonrshar.pe/images/cypress-gui.png"&gt;&lt;/p&gt;
&lt;p&gt;Click "E2E Testing", which will also create a &lt;code&gt;cypress.config.js&lt;/code&gt; configuration file (mostly just an empty object, you can see the configuration options &lt;a href="https://docs.cypress.io/guides/references/configuration.html"&gt;in the docs&lt;/a&gt;) and a &lt;code&gt;cypress/&lt;/code&gt; directory. Quit the UI then get rid of the example fixture:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;./cypress/fixtures/example.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Add a script to run the E2E tests (note we're now using &lt;code&gt;run&lt;/code&gt;, rather than &lt;code&gt;open&lt;/code&gt; - you can read more about the commands &lt;a href="https://docs.cypress.io/guides/guides/command-line.html"&gt;in the docs&lt;/a&gt;) into the package file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;scripts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;e2e&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cypress run&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;start&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;react-scripts start&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;build&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;react-scripts build&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;react-scripts test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;eject&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;react-scripts eject&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and run our (missing!) tests:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;e2e

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-e2e@0.1.0&lt;span class="w"&gt; &lt;/span&gt;e2e
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;cypress&lt;span class="w"&gt; &lt;/span&gt;run


DevTools&lt;span class="w"&gt; &lt;/span&gt;listening&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;ws://127.0.0.1:50454/devtools/browser/9877dd09-85f3-4703-b3f4-8849b8f72424
Couldn&lt;span class="s1"&gt;&amp;#39;t find tsconfig.json. tsconfig-paths will be skipped&lt;/span&gt;
&lt;span class="s1"&gt;Can&amp;#39;&lt;/span&gt;t&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;because&lt;span class="w"&gt; &lt;/span&gt;no&lt;span class="w"&gt; &lt;/span&gt;spec&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;were&lt;span class="w"&gt; &lt;/span&gt;found.

We&lt;span class="w"&gt; &lt;/span&gt;searched&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;specs&lt;span class="w"&gt; &lt;/span&gt;matching&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;glob&lt;span class="w"&gt; &lt;/span&gt;pattern:

&lt;span class="w"&gt;  &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-e2e/cypress/e2e/**/*.cy.&lt;span class="o"&gt;{&lt;/span&gt;js,jsx,ts,tsx&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cypress refuses to run at all if it can't find any test files, so let's create an empty test file using &lt;code&gt;touch&lt;/code&gt; and re-run our now-present (but still empty) test suite:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;./cypress/e2e/

$&lt;span class="w"&gt; &lt;/span&gt;touch&lt;span class="w"&gt; &lt;/span&gt;./cypress/e2e/journey.cy.js

$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;e2e

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-e2e@0.1.0&lt;span class="w"&gt; &lt;/span&gt;e2e
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;cypress&lt;span class="w"&gt; &lt;/span&gt;run


DevTools&lt;span class="w"&gt; &lt;/span&gt;listening&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;ws://127.0.0.1:50484/devtools/browser/f3150f98-9a5d-4843-85bf-ad581eacc1ae
Couldn&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;tsconfig.json.&lt;span class="w"&gt; &lt;/span&gt;tsconfig-paths&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;skipped&lt;/span&gt;

&lt;span class="o"&gt;====================================================================================================&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;Starting&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Cypress:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;.16.0&lt;span class="w"&gt;                                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Browser:&lt;span class="w"&gt;        &lt;/span&gt;Electron&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;106&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;headless&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Node&lt;span class="w"&gt; &lt;/span&gt;Version:&lt;span class="w"&gt;   &lt;/span&gt;v16.20.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/node&lt;span class="o"&gt;)&lt;/span&gt;.&lt;span class="w"&gt;                                                       &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Specs:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;journey.cy.js&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Searched:&lt;span class="w"&gt;       &lt;/span&gt;cypress/e2e/**/*.cy.&lt;span class="o"&gt;{&lt;/span&gt;js,jsx,ts,tsx&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt;                                            &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────────────────────────────────

&lt;span class="w"&gt;  &lt;/span&gt;Running:&lt;span class="w"&gt;  &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                                                                   &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passing&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1ms&lt;span class="o"&gt;)&lt;/span&gt;

Warning:&lt;span class="w"&gt; &lt;/span&gt;We&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;capturing&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;video.

This&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;affect&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;change&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;code.

TimeoutError:&lt;span class="w"&gt; &lt;/span&gt;operation&lt;span class="w"&gt; &lt;/span&gt;timed&lt;span class="w"&gt; &lt;/span&gt;out
&lt;span class="w"&gt;    &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;afterTimeout&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/Cypress/12.16.0/Cypress.app/Contents/Resources/app/node_modules/bluebird/js/release/timers.js:46:19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Timeout.timeoutTimeout&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;as&lt;span class="w"&gt; &lt;/span&gt;_onTimeout&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/Cypress/12.16.0/Cypress.app/Contents/Resources/app/node_modules/bluebird/js/release/timers.js:76:13&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;listOnTimeout&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/timers:559:17&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;process.processTimers&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;node:internal/timers:502:7&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Results&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tests:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Passing:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Failing:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Pending:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Skipped:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Screenshots:&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Video:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;                                                                            &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Duration:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;seconds&lt;span class="w"&gt;                                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Spec&lt;span class="w"&gt; &lt;/span&gt;Ran:&lt;span class="w"&gt;     &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                                                                    &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘


&lt;span class="o"&gt;====================================================================================================&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;Finished&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="w"&gt;       &lt;/span&gt;Spec&lt;span class="w"&gt;                                              &lt;/span&gt;Tests&lt;span class="w"&gt;  &lt;/span&gt;Passing&lt;span class="w"&gt;  &lt;/span&gt;Failing&lt;span class="w"&gt;  &lt;/span&gt;Pending&lt;span class="w"&gt;  &lt;/span&gt;Skipped&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;✔&lt;span class="w"&gt;  &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                              &lt;/span&gt;0ms&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘
&lt;span class="w"&gt;    &lt;/span&gt;✔&lt;span class="w"&gt;  &lt;/span&gt;All&lt;span class="w"&gt; &lt;/span&gt;specs&lt;span class="w"&gt; &lt;/span&gt;passed!&lt;span class="w"&gt;                          &lt;/span&gt;0ms&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Unlike Jest, Cypress doesn't mind if there aren't any tests in the files and considers that a successful run. Note it also tried to create a video of the run; Cypress can create both videos and screenshots to help with debugging tests, as well as storing any files downloaded as part of a test. We don't want to track all of these in git, though, so add the following to &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# cypress&lt;/span&gt;
&lt;span class="n"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;downloads&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="n"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;screenshots&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="n"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;videos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Before we write our first test, let's make a commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;.gitignore
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;cypress.config.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;cypress/e2e/journey.cy.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;cypress/support/commands.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;cypress/support/e2e.js
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;package-lock.json
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;package.json

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Install and configure Cypress&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;7da2ace&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Install&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;configure&lt;span class="w"&gt; &lt;/span&gt;Cypress
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2401&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;252&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cypress.config.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cypress/e2e/journey.cy.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cypress/support/commands.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cypress/support/e2e.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Writing the E2E test [3/8]&lt;/h2&gt;
&lt;p&gt;Let's load Cypress Testing Library for the tests by adding the following import to &lt;code&gt;cypress/support/commands.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@testing-library/cypress/add-commands&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we want to actually visit our page. The best practice, &lt;a href="https://docs.cypress.io/guides/references/best-practices.html#Setting-a-global-baseUrl"&gt;per the docs&lt;/a&gt;, is to configure a global base URL and navigate relative to that, so let's add the default CRA URL (along with disabling the video recordings, to simplify the outputs) to &lt;code&gt;cypress.config.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;module.exports = defineConfig({
&lt;span class="w"&gt; &lt;/span&gt;  e2e: {
&lt;span class="gi"&gt;+    baseUrl: &amp;quot;http://localhost:3000&amp;quot;,&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    setupNodeEvents(on, config) {
&lt;span class="w"&gt; &lt;/span&gt;      // implement node event listeners here
&lt;span class="w"&gt; &lt;/span&gt;    },
&lt;span class="gi"&gt;+    video: false,&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  },
&lt;span class="w"&gt; &lt;/span&gt;});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Just like with Jest, Cypress provides an &lt;code&gt;it&lt;/code&gt; function for registering a test, again taking the name of the test as a string and the body of the test as a function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should say left wins for rock vs. scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Arrange&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Act&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Throw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Assert&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;outcome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;contain.text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Left wins!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(If your IDE seems unhappy with &lt;code&gt;cy&lt;/code&gt;, just ignore it for now, but check out the bonus section on Linting at the end of the article.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is basically the same expectation as the first unit test case we wrote last time, but at the end-to-end level. &lt;code&gt;cy&lt;/code&gt; is a global object that provides access to various Cypress methods; this is a pretty big API (and we've added more things to it from Testing Library!) so to translate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cy.visit("/");&lt;/code&gt; - visit the root path, based on the base URL we already set. This should take us to the home page of our site;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cy.findByLabelText("Left").select("rock");&lt;/code&gt; - find a control with the label "Left" and select the "rock" option;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cy.findByLabelText("Right").select("scissors");&lt;/code&gt; - find a control with the label "Right" and select the "scissors" option;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cy.findByText("Throw").click();&lt;/code&gt; - find a button that says "Throw" and click it; and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cy.findByTestId("outcome").should("contain.text", "Left wins!");&lt;/code&gt; - check that the outcome being being displayed contains the expected text &lt;code&gt;Left wins!&lt;/code&gt;, using a Chai DOM assertion &lt;a href="https://docs.cypress.io/guides/references/assertions.html"&gt;exposed by Cypress&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; that we look for the outcome in an element with the appropriate &lt;em&gt;test ID&lt;/em&gt;; using stable attribute selectors is another &lt;a href="https://docs.cypress.io/guides/references/best-practices.html#Selecting-Elements"&gt;Cypress best practice&lt;/a&gt; and Testing Library gives us functions to easily access such elements, assuming the attribute is named &lt;code&gt;data-testid&lt;/code&gt; (although this is configurable). In this case, we'd expect an element like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;data-testid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;outcome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello, world!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As before, &lt;em&gt;none of this exists yet&lt;/em&gt;, so we can easily talk about how this user interface should work without the friction of having to implement it. Maybe the user should enter free text instead of selecting from a list? Maybe it should automatically show the outcome when the second input is provided, rather than requiring a button click? Maybe there should be names for the users instead of left and right? This is making us think in concrete terms about how the users should interact with the system, early in the process. In this case, we're describing something like: &lt;/p&gt;
&lt;p&gt;&lt;img alt="Wireframe of the proposed RPS UI" src="https://blog.jonrshar.pe/images/rps-ui.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;Created with &lt;a href="https://www.lofiwireframekit.com/"&gt;lofiwireframekit.com&lt;/a&gt;.&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Before we continue, think about how you might actually implement that UI in React - what components would you have, how would they interact, where would the state live? Note your ideas down, we'll revisit them later.&lt;/p&gt;
&lt;p&gt;Just like with Jest, call the shot then run the tests:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;e2e

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-e2e@0.1.0&lt;span class="w"&gt; &lt;/span&gt;e2e
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;cypress&lt;span class="w"&gt; &lt;/span&gt;run


DevTools&lt;span class="w"&gt; &lt;/span&gt;listening&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;ws://127.0.0.1:50699/devtools/browser/0ed01742-886c-4a1b-9abe-eccf1e79372e
Couldn&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;tsconfig.json.&lt;span class="w"&gt; &lt;/span&gt;tsconfig-paths&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;skipped
Cypress&lt;span class="w"&gt; &lt;/span&gt;could&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;verify&lt;span class="w"&gt; &lt;/span&gt;that&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;server&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;running:

&lt;span class="w"&gt;  &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;http://localhost:3000

We&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;verifying&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;server&lt;span class="w"&gt; &lt;/span&gt;because&lt;span class="w"&gt; &lt;/span&gt;it&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;been&lt;span class="w"&gt; &lt;/span&gt;configured&lt;span class="w"&gt; &lt;/span&gt;as&lt;span class="w"&gt; &lt;/span&gt;your&lt;span class="w"&gt; &lt;/span&gt;baseUrl.

Cypress&lt;span class="w"&gt; &lt;/span&gt;automatically&lt;span class="w"&gt; &lt;/span&gt;waits&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;until&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;your&lt;span class="w"&gt; &lt;/span&gt;server&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;accessible&lt;span class="w"&gt; &lt;/span&gt;before&lt;span class="w"&gt; &lt;/span&gt;running&lt;span class="w"&gt; &lt;/span&gt;tests.

We&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;try&lt;span class="w"&gt; &lt;/span&gt;connecting&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;it&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;times...
We&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;try&lt;span class="w"&gt; &lt;/span&gt;connecting&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;it&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;times...
We&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;try&lt;span class="w"&gt; &lt;/span&gt;connecting&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;it&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;time...

Cypress&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;verify&lt;span class="w"&gt; &lt;/span&gt;that&lt;span class="w"&gt; &lt;/span&gt;your&lt;span class="w"&gt; &lt;/span&gt;server&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;running.

Please&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;server&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;Cypress&lt;span class="w"&gt; &lt;/span&gt;again.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cypress is unhappy because we're &lt;em&gt;not actually running the app&lt;/em&gt;, so it can't find the specified base URL. As a simple fix, open an extra command line, navigate to the working directory and run &lt;code&gt;npm start&lt;/code&gt;. For more information on CRA's future, see &lt;a href="https://github.com/reactjs/react.dev/pull/5487#issuecomment-1409720741"&gt;this issue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: at this point you may see an error like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;One&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;babel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;preset&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;react&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;importing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;
&lt;span class="ss"&gt;&amp;quot;@babel/plugin-proposal-private-property-in-object&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;without&lt;/span&gt;
&lt;span class="n"&gt;declaring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;its&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;currently&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;working&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;because&lt;/span&gt;
&lt;span class="ss"&gt;&amp;quot;@babel/plugin-proposal-private-property-in-object&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;already&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;
&lt;span class="n"&gt;node_modules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unrelated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;may&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;babel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;preset&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;react&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;create&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;react&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;which&lt;/span&gt;
&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;maintianed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;anymore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;It&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unlikely&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;will&lt;/span&gt;
&lt;span class="n"&gt;ever&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;@babel/plugin-proposal-private-property-in-object&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;
&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;devDependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;work&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;around&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;away&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;if you do, just &lt;code&gt;npm install @babel/plugin-proposal-private-property-in-object&lt;/code&gt; and continue.&lt;/p&gt;
&lt;p&gt;Once the default CRA home screen shows up in your browser, call the shot then run the E2E tests again in your first command line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;e2e

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-e2e@0.1.0&lt;span class="w"&gt; &lt;/span&gt;e2e
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;cypress&lt;span class="w"&gt; &lt;/span&gt;run


DevTools&lt;span class="w"&gt; &lt;/span&gt;listening&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;ws://127.0.0.1:50726/devtools/browser/7ea7c9ee-025d-4b38-b5b6-74bdd9b08adf
Couldn&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;tsconfig.json.&lt;span class="w"&gt; &lt;/span&gt;tsconfig-paths&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;skipped&lt;/span&gt;

&lt;span class="o"&gt;====================================================================================&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;Starting&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Cypress:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;.16.0&lt;span class="w"&gt;                                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Browser:&lt;span class="w"&gt;        &lt;/span&gt;Electron&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;106&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;headless&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Node&lt;span class="w"&gt; &lt;/span&gt;Version:&lt;span class="w"&gt;   &lt;/span&gt;v16.20.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/node&lt;span class="o"&gt;)&lt;/span&gt;.&lt;span class="w"&gt;                                                       &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Specs:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;journey.cy.js&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Searched:&lt;span class="w"&gt;       &lt;/span&gt;cypress/e2e/**/*.cy.&lt;span class="o"&gt;{&lt;/span&gt;js,jsx,ts,tsx&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt;                                            &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────────────────────────────────

&lt;span class="w"&gt;  &lt;/span&gt;Running:&lt;span class="w"&gt;  &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                                                                   &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passing&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;4s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failing

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors:
&lt;span class="w"&gt;     &lt;/span&gt;AssertionError:&lt;span class="w"&gt; &lt;/span&gt;Timed&lt;span class="w"&gt; &lt;/span&gt;out&lt;span class="w"&gt; &lt;/span&gt;retrying&lt;span class="w"&gt; &lt;/span&gt;after&lt;span class="w"&gt; &lt;/span&gt;4000ms:&lt;span class="w"&gt; &lt;/span&gt;Unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;label&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;text&lt;span class="w"&gt; &lt;/span&gt;of:&lt;span class="w"&gt; &lt;/span&gt;Left

Ignored&lt;span class="w"&gt; &lt;/span&gt;nodes:&lt;span class="w"&gt; &lt;/span&gt;comments,&lt;span class="w"&gt; &lt;/span&gt;script,&lt;span class="w"&gt; &lt;/span&gt;style
&amp;lt;html
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;
&amp;gt;
&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;head&amp;gt;



&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;link
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/favicon.ico&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;icon&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;width=device-width, initial-scale=1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;viewport&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#000000&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;theme-color&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;meta
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Web site created using create-react-app&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;link
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/logo192.png&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;apple-touch-icon&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;




&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;link
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/manifest.json&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;manifest&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/&amp;gt;




&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;title&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;React&lt;span class="w"&gt; &lt;/span&gt;App
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/title&amp;gt;


&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;/head&amp;gt;


&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;body&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;noscript&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;You&lt;span class="w"&gt; &lt;/span&gt;need&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;JavaScript&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;app.
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/noscript&amp;gt;


&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;div
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;root&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;lt;div
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;header
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App-header&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;img
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;logo&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App-logo&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;/&amp;gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;p&amp;gt;
&lt;span class="w"&gt;            &lt;/span&gt;Edit&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&amp;lt;code&amp;gt;
&lt;span class="w"&gt;              &lt;/span&gt;src/App.js
&lt;span class="w"&gt;            &lt;/span&gt;&amp;lt;/code&amp;gt;
&lt;span class="w"&gt;             &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;save&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;reload.
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;/p&amp;gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;a
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App-link&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://reactjs.org&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;noopener noreferrer&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;_blank&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&amp;gt;
&lt;span class="w"&gt;            &lt;/span&gt;Learn&lt;span class="w"&gt; &lt;/span&gt;React
&lt;span class="w"&gt;          &lt;/span&gt;&amp;lt;/a&amp;gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;/header&amp;gt;
&lt;span class="w"&gt;      &lt;/span&gt;&amp;lt;/div&amp;gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;/div&amp;gt;






&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;...
&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Context.eval&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;webpack:///./cypress/e2e/journey.cy.js:6:5&lt;span class="o"&gt;)&lt;/span&gt;




&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Results&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tests:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Passing:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Failing:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Pending:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Skipped:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Screenshots:&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Video:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;                                                                            &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Duration:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;seconds&lt;span class="w"&gt;                                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Spec&lt;span class="w"&gt; &lt;/span&gt;Ran:&lt;span class="w"&gt;     &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                                                                    &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘


&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Screenshots&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt;  &lt;/span&gt;path/to/rps-e2e/cypress/screenshots/journey.cy.js/should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1280x720&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;failed&lt;span class="o"&gt;)&lt;/span&gt;.png&lt;span class="w"&gt;                                     &lt;/span&gt;


&lt;span class="o"&gt;====================================================================================&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;Finished&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="w"&gt;       &lt;/span&gt;Spec&lt;span class="w"&gt;                                              &lt;/span&gt;Tests&lt;span class="w"&gt;  &lt;/span&gt;Passing&lt;span class="w"&gt;  &lt;/span&gt;Failing&lt;span class="w"&gt;  &lt;/span&gt;Pending&lt;span class="w"&gt;  &lt;/span&gt;Skipped&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;✖&lt;span class="w"&gt;  &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;:04&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘
&lt;span class="w"&gt;    &lt;/span&gt;✖&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;:04&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;OK, we've moved on a step - the test is now failing because it can't find the element on the page. So far we haven't actually added anything to the page, so that makes sense, it's just showing the default CRA info (you can see this in the screenshot Cypress took, check it out!)&lt;/p&gt;
&lt;p&gt;This is going to be our guiding star for the rest of the exercise, so let's make a commit to store this state:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;cypress.config.js
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;cypress/e2e/journey.cy.js
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;cypress/support/commands.js
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;package-lock.json
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;package.json

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Implement E2E test&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;ee33a63&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Implement&lt;span class="w"&gt; &lt;/span&gt;E2E&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;53&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Moving to the integration level [4/8]&lt;/h2&gt;
&lt;p&gt;We're working our way from the outside in, and we have a failing E2E test, so let's write an &lt;em&gt;integration&lt;/em&gt; test in Jest. Replace the content of &lt;code&gt;./src/App.test.js&lt;/code&gt; with the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@testing-library/react&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@testing-library/user-event&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./App&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App component&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;displays right wins when appropriate&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Arrange&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;App&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Act&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Throw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Assert&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;outcome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Right wins!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The way we express the steps might be slightly different, but note that the &lt;em&gt;logic&lt;/em&gt; is exactly the same as we tested at the end-to-end level:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;const user = userEvent.setup();&lt;/code&gt; - start a user event session in the document to be tested;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;render(&amp;lt;App /&amp;gt;);&lt;/code&gt; - render the &lt;code&gt;App&lt;/code&gt; component, equivalent to visiting the page;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;await user.selectOptions(screen.getByLabelText("Left"), "paper");&lt;/code&gt; - find a control with the label "Left" and select the "paper" option;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;await user.selectOptions(screen.getByLabelText("Right"), "scissors");&lt;/code&gt; - find a control with the label "Right" and select the "scissors" option;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;await user.click(screen.getByText("Throw"));&lt;/code&gt; - find a button that says "Throw" and click it; and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;expect(screen.getByTestId("outcome")).toHaveTextContent("Right wins!");&lt;/code&gt; - check that the outcome being being displayed contains the expected text &lt;code&gt;Right wins!&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Call the shot and run the test (note I'm using the environment variable &lt;code&gt;CI=true&lt;/code&gt; to run the tests once and exit; you can use the default &lt;code&gt;npm test&lt;/code&gt; to enter watch mode if you'd prefer, or &lt;code&gt;npm test -- --watchAll false&lt;/code&gt; as an alternative to setting &lt;code&gt;CI&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e2e&lt;/span&gt;&lt;span class="mf"&gt;@0.1.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;react&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;scripts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;

&lt;span class="n"&gt;FAIL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;component&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;✕&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;displays&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;appropriate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;●&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;component&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;›&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;displays&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;appropriate&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;TestingLibraryElementError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Unable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Left&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Ignored&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;App&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;App-header&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;logo&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;App-logo&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;logo.svg&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="n"&gt;Edit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="n"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;App-link&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://reactjs.org&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;noopener noreferrer&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_blank&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="n"&gt;Learn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;React&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;// Act&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selectOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                                     &lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selectOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Throw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getElementError&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_modules&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dom&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getAllByLabelText&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_modules&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dom&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;111&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node_modules&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dom&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;helpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getByLabelText&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_modules&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dom&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;helpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;anonymous&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TestScheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheduleTests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_modules&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;jest&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;TestScheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;333&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;runJest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_modules&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;jest&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;runJest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_run10000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_modules&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;jest&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;cli&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;320&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;runCLI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_modules&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;jest&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;cli&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;173&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Suites&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="nl"&gt;Tests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="nl"&gt;Snapshots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="nl"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mf"&gt;0.709&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;estimated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="n"&gt;Ran&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;suites&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Compare the error messages; they're failing on the same problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;E2E&lt;/strong&gt;: &lt;code&gt;AssertionError: Timed out retrying after 4000ms: Unable to find a label with the text of: Left&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration&lt;/strong&gt;: &lt;code&gt;TestingLibraryElementError: Unable to find a label with the text of: Left&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of a screenshot we get the rendered HTML, but it shows a similar thing - the default CRA content is still being shown. You should also see that running this test was &lt;strong&gt;much faster&lt;/strong&gt; than starting up the app and running Cypress. Lower level tests tend to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;be &lt;em&gt;more coupled to the implementation&lt;/em&gt; (this one knows our app is using React, which Cypress had no idea about); but&lt;/li&gt;
&lt;li&gt;have a &lt;em&gt;shorter feedback loop&lt;/em&gt; (by orders of magnitude, the integration test itself took 54ms vs. 4s for the E2E).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let's save this new state:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;src/App.test.js

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Implement integration test&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1585130&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Implement&lt;span class="w"&gt; &lt;/span&gt;integration&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Before we move in one last level, to the unit tests, let's think about how our app might be structured. Again note that we can have this discussion before actually writing anything, because the need to identify our test boundaries is driving us to think about the architecture. We have two main concerns here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the &lt;em&gt;business logic&lt;/em&gt;, determining a winner given two weapons (we tested and implemented this in the previous article) - this can be implemented as a &lt;em&gt;service&lt;/em&gt;; and&lt;/li&gt;
&lt;li&gt;the &lt;em&gt;user interface&lt;/em&gt;, taking user input and showing the winner - this can be implemented as &lt;em&gt;components&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;React apps tend to have a root &lt;code&gt;App&lt;/code&gt; component, which we could use as a coordinator here. It will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;render a form with our input controls (two &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;s and a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;communicate with the service (RPS logic we already have); and&lt;/li&gt;
&lt;li&gt;display the outcome.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can model this as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;Service&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Outcome&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We already have an integration test covering these parts working together, but we can create unit tests for the service and the low-level components.&lt;/p&gt;
&lt;h2&gt;At your service [5/8]&lt;/h2&gt;
&lt;p&gt;We already have this! You should still have a function named &lt;code&gt;rps&lt;/code&gt; from the previous article, along with a suite of tests. Place the function in a file named &lt;code&gt;./src/rpsService.js&lt;/code&gt; and export it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;then place the test suite in a file named &lt;code&gt;./src/rpsService.test.js&lt;/code&gt; along with an import:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./rpsService&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock, paper, scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;All of the same tests should pass happily in the new context. Once all of the service tests are passing (although &lt;code&gt;./src/App.test.js&lt;/code&gt; will still fail), commit it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;main
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/rpsService.js
&lt;span class="w"&gt;        &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/rpsService.test.js

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Migrate tested service logic&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main&lt;span class="w"&gt; &lt;/span&gt;732e441&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Migrate&lt;span class="w"&gt; &lt;/span&gt;tested&lt;span class="w"&gt; &lt;/span&gt;service&lt;span class="w"&gt; &lt;/span&gt;logic
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;75&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/rpsService.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/rpsService.test.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Component unit tests [6/8]&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Outcome&lt;/code&gt; component is going to be very simple, as it only has to display the text we want given a result, so let's start with that. Add the following to &lt;code&gt;./src/Outcome.test.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@testing-library/react&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Outcome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./Outcome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;App component&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;displays &amp;#39;Right wins!&amp;#39; when right wins&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Outcome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;outcome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Right wins!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once again we can talk about the interface before we're tied to an implementation. In this case, I've assumed the component will have a single prop named &lt;code&gt;result&lt;/code&gt;, that matches the value returned from the service, and will render an element with &lt;code&gt;data-testid="outcome"&lt;/code&gt;, to match our higher-level tests, with the expected text.&lt;/p&gt;
&lt;p&gt;Call the shot and run the test. Initially it will fail because Jest &lt;code&gt;Cannot find module './Outcome' from 'src/Outcome.test.js'&lt;/code&gt; - that should make sense, we haven't created that file yet. Go through the process of making small changes and re-running the test until it passes, with the &lt;em&gt;simplest possible implementation&lt;/em&gt;. Remember to call the shot before each test run, and aim to change the error you get step by step rather than just jumping to the passing test.&lt;/p&gt;
&lt;p&gt;At this point, you should have something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Outcome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;data-testid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;outcome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wins&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you have any logic in your component, go back! It's too complicated; remember to write the simplest code that passes the tests and let additional test cases force you to add complexity.&lt;/p&gt;
&lt;p&gt;Make a commit to save your progress, with a message like &lt;em&gt;"Implement Outcome for right winning"&lt;/em&gt;. Now repeat the process for each of the following tests, one at a time: add the test case; call the shot; get it passing; refactor as desired; make a commit with a sensible message.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Left wins:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;displays &amp;#39;Left wins!&amp;#39; when left wins&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Outcome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;outcome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Left wins!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Draw:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;displays &amp;#39;Draw!&amp;#39; when there&amp;#39;s a draw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Outcome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;draw&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;outcome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Draw!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once all three tests are passing, we can move on to the next component. Add the following to &lt;code&gt;./src/Form.test.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@testing-library/react&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@testing-library/user-event&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./Form&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Form component&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;emits a pair of selections when the form is submitted&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Throw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This looks quite a lot like the end-to-end and integration tests, but with a more limited scope - we only care that the user input is taken correctly, not that the appropriate winner is determined. This is part of the process of &lt;em&gt;decomposing&lt;/em&gt; the problem into smaller (and easier-to-solve) pieces, which is the reason I think starting with the end-to-end tests makes sense.&lt;/p&gt;
&lt;p&gt;It also introduces a Jest &lt;em&gt;"mock function"&lt;/em&gt;, a &lt;a href="https://tanzu.vmware.com/content/pivotal-engineering-journal/the-test-double-rule-of-thumb-2"&gt;test double&lt;/a&gt; we can pass to the &lt;code&gt;Form&lt;/code&gt; component in place of a real function prop. The expectation here is that this mock function gets called with the appropriate values, as this is how the data will be passed up to the &lt;code&gt;App&lt;/code&gt; component.&lt;/p&gt;
&lt;p&gt;Again this gives us an opportunity to talk about the API details of the component before it even exists. Perhaps it should return an object &lt;code&gt;{ left, right }&lt;/code&gt; instead of an array &lt;code&gt;[left, right]&lt;/code&gt;? Is &lt;code&gt;onSubmit&lt;/code&gt; the best name for the prop? It's easier to have these discussions when changing the API is a matter of changing your mind rather than changing the code.&lt;/p&gt;
&lt;p&gt;Repeat the usual process until you get this test passing. Covering the details of how to implement something like this in React are a bit beyond the scope of this article, but one way is to create some &lt;a href="https://react.dev/reference/react-dom/components/input#controlling-an-input-with-a-state-variable"&gt;controlled components&lt;/a&gt; that update the component's state. Once it passes, make another commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;master
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/Form.js
&lt;span class="w"&gt;    &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;file:&lt;span class="w"&gt;   &lt;/span&gt;src/Form.test.js


$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Implement Form component&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;eca31d7&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Implement&lt;span class="w"&gt; &lt;/span&gt;Form&lt;span class="w"&gt; &lt;/span&gt;component
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;46&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/Form.js
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/Form.test.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Putting it all back together [7/8]&lt;/h2&gt;
&lt;p&gt;Now we have a bunch of well-tested components and a service, but our integration and E2E tests are still failing, so it's time to bring everything together. Given that we already have two layers of testing for &lt;code&gt;./src/App.js&lt;/code&gt; and most of the work is done elsewhere let's not add unit tests too; something like the following should be enough to get everything passing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;react&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./Form&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Outcome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./Outcome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./rpsService&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;onThrow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onThrow&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Outcome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot and run the whole suite:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-e2e@0.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-e2e
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;react-scripts&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;

PASS&lt;span class="w"&gt; &lt;/span&gt;src/Outcome.test.js
PASS&lt;span class="w"&gt; &lt;/span&gt;src/App.test.js
PASS&lt;span class="w"&gt; &lt;/span&gt;src/Form.test.js
PASS&lt;span class="w"&gt; &lt;/span&gt;src/rpsService.test.js

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.826&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;All of the unit tests should now be passing, so call the final shot and run the end-to-end test:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;e2e

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-e2e@0.1.0&lt;span class="w"&gt; &lt;/span&gt;e2e
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;cypress&lt;span class="w"&gt; &lt;/span&gt;run


DevTools&lt;span class="w"&gt; &lt;/span&gt;listening&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;ws://127.0.0.1:54317/devtools/browser/fb0390d2-e65c-4fa8-bf24-27d80cf3928e
Couldn&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;tsconfig.json.&lt;span class="w"&gt; &lt;/span&gt;tsconfig-paths&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;skipped&lt;/span&gt;

&lt;span class="o"&gt;====================================================================================&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;Starting&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Cypress:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;.16.0&lt;span class="w"&gt;                                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Browser:&lt;span class="w"&gt;        &lt;/span&gt;Electron&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;106&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;headless&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Node&lt;span class="w"&gt; &lt;/span&gt;Version:&lt;span class="w"&gt;   &lt;/span&gt;v16.20.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;path/to/node&lt;span class="o"&gt;)&lt;/span&gt;.&lt;span class="w"&gt;                                                       &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Specs:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;journey.cy.js&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Searched:&lt;span class="w"&gt;       &lt;/span&gt;cypress/e2e/**/*.cy.&lt;span class="o"&gt;{&lt;/span&gt;js,jsx,ts,tsx&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt;                                            &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────────────────────────────────

&lt;span class="w"&gt;  &lt;/span&gt;Running:&lt;span class="w"&gt;  &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                                                                   &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="w"&gt;  &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;602ms&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passing&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;618ms&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Results&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tests:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Passing:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Failing:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Pending:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Skipped:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Screenshots:&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                                                                                &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Video:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;                                                                            &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Duration:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;seconds&lt;span class="w"&gt;                                                                        &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Spec&lt;span class="w"&gt; &lt;/span&gt;Ran:&lt;span class="w"&gt;     &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                                                                    &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘


&lt;span class="o"&gt;====================================================================================&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;Finished&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="w"&gt;       &lt;/span&gt;Spec&lt;span class="w"&gt;                                              &lt;/span&gt;Tests&lt;span class="w"&gt;  &lt;/span&gt;Passing&lt;span class="w"&gt;  &lt;/span&gt;Failing&lt;span class="w"&gt;  &lt;/span&gt;Pending&lt;span class="w"&gt;  &lt;/span&gt;Skipped&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;┌────────────────────────────────────────────────────────────────────────────────────────────────┐
&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;✔&lt;span class="w"&gt;  &lt;/span&gt;journey.cy.js&lt;span class="w"&gt;                            &lt;/span&gt;616ms&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;│
&lt;span class="w"&gt;  &lt;/span&gt;└────────────────────────────────────────────────────────────────────────────────────────────────┘
&lt;span class="w"&gt;    &lt;/span&gt;✔&lt;span class="w"&gt;  &lt;/span&gt;All&lt;span class="w"&gt; &lt;/span&gt;specs&lt;span class="w"&gt; &lt;/span&gt;passed!&lt;span class="w"&gt;                        &lt;/span&gt;616ms&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;-&lt;span class="w"&gt;  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's it! We've created a simple UI for our RPS implementation, test-driving it from the outside in. Create a commit to save this work:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.

$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;master
Changes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore --staged &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;unstage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;src/App.js


$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Complete RPS UI implementation&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;c6cd9a8&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Complete&lt;span class="w"&gt; &lt;/span&gt;RPS&lt;span class="w"&gt; &lt;/span&gt;UI&lt;span class="w"&gt; &lt;/span&gt;implementation
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;rewrite&lt;span class="w"&gt; &lt;/span&gt;src/App.js&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;90&lt;/span&gt;%&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now reflect on the exercise - how does the implementation compare to what you'd initially imagined? What felt good or bad about the process?&lt;/p&gt;
&lt;p&gt;You can see my copy of this exercise at &lt;a href="https://github.com/textbook/rps-e2e"&gt;https://github.com/textbook/rps-e2e&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Exercises [8/8]&lt;/h2&gt;
&lt;p&gt;Here are some additional exercises you can run through:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Repeat the process from the beginning and try to come up with a different implementation (including running through the core service logic, rather than copying it over). Was your new route easier or harder?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I mentioned various alternatives to the user interface we implemented, e.g. allowing free text input. Pick one of my suggestions (or come up with your own) and implement it from the outside in.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you implemented additional weapons in your &lt;code&gt;rps&lt;/code&gt; implementation, extend the UI to support them. If not, maybe this is a good time to revisit it!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The UI is pretty basic - we've tested the &lt;em&gt;functionality&lt;/em&gt; but said nothing about how it should look. Improve the styling while keeping the tests passing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Repeat the exercise without writing &lt;em&gt;any&lt;/em&gt; unit-level tests; use the same end-to-end test then drive everything else from the integration level. What does this make easier and harder?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are a bunch of unhappy paths and un-/under-specified/edge cases in this implementation. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Which weapons should be selected by default? If "none", what should happen when the Throw button is clicked and one or both weapons are still unselected?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What should happen if one of the weapons is changed after the Throw button is clicked?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What should happen when the page is refreshed?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pick one or more of these. Which part(s) of the React app (currently &lt;code&gt;App&lt;/code&gt;, &lt;code&gt;Form&lt;/code&gt;, &lt;code&gt;Outcome&lt;/code&gt; or &lt;code&gt;rpsService&lt;/code&gt;) should be responsible for dealing with it? Write an E2E test case for the scenario, then use integration and/or unit tests to test drive the implementation in whichever part you choose.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pick another simple TDD task (e.g. FizzBuzz, BMI calculator, ...) and use these techniques to test drive a React UI for it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I'd recommend creating a new git branch for each one you try (e.g. use &lt;code&gt;git checkout -b &amp;lt;name&amp;gt;&lt;/code&gt;) and making commits as appropriate.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Once you're ready to move on&lt;/strong&gt;, check out &lt;a href="https://blog.jonrshar.pe/2021/Apr/10/js-tdd-api.html"&gt;the next article&lt;/a&gt; in this series where we'll learn more about how to deal with sources of data outside of our control.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Linting [Bonus]&lt;/h2&gt;
&lt;p&gt;Depending on your setup, you may have noticed that your IDE was warning that &lt;code&gt;cy&lt;/code&gt; is undefined; the default CRA linting settings include the &lt;code&gt;no-undef&lt;/code&gt; rule and there's nothing to tell ESLint that &lt;code&gt;cy&lt;/code&gt; is going to be defined. To be able to easily run the linter, add another script to the package file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;scripts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;e2e&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cypress run&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;lint&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;eslint cypress/ src/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;start&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;react-scripts start&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;build&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;react-scripts build&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;react-scripts test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;eject&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;react-scripts eject&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can now run this to see the problem:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;lint

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-e2e@0.1.0&lt;span class="w"&gt; &lt;/span&gt;lint&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-e2e
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;eslint&lt;span class="w"&gt; &lt;/span&gt;cypress/&lt;span class="w"&gt; &lt;/span&gt;src/


path/to/rps-e2e/cypress/integration/e2e.test.js
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;:3&lt;span class="w"&gt;  &lt;/span&gt;error&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cy&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;defined&lt;span class="w"&gt;  &lt;/span&gt;no-undef
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;:3&lt;span class="w"&gt;  &lt;/span&gt;error&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cy&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;defined&lt;span class="w"&gt;  &lt;/span&gt;no-undef
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;:3&lt;span class="w"&gt;  &lt;/span&gt;error&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cy&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;defined&lt;span class="w"&gt;  &lt;/span&gt;no-undef
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;:3&lt;span class="w"&gt;  &lt;/span&gt;error&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cy&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;defined&lt;span class="w"&gt;  &lt;/span&gt;no-undef
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;:3&lt;span class="w"&gt;  &lt;/span&gt;error&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cy&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;defined&lt;span class="w"&gt;  &lt;/span&gt;no-undef

✖&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;problems&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;errors,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;warnings&lt;span class="o"&gt;)&lt;/span&gt;

npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;code&lt;span class="w"&gt; &lt;/span&gt;ELIFECYCLE
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;errno&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;rps-e2e@0.1.0&lt;span class="w"&gt; &lt;/span&gt;lint:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;eslint&lt;span class="w"&gt; &lt;/span&gt;cypress/&lt;span class="w"&gt; &lt;/span&gt;src/&lt;span class="sb"&gt;`&lt;/span&gt;
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;Exit&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;Failed&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;rps-e2e@0.1.0&lt;span class="w"&gt; &lt;/span&gt;lint&lt;span class="w"&gt; &lt;/span&gt;script.
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;This&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;probably&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;problem&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;npm.&lt;span class="w"&gt; &lt;/span&gt;There&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;likely&lt;span class="w"&gt; &lt;/span&gt;additional&lt;span class="w"&gt; &lt;/span&gt;logging&lt;span class="w"&gt; &lt;/span&gt;output&lt;span class="w"&gt; &lt;/span&gt;above.

npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;A&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;log&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;can&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;:
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt;     &lt;/span&gt;path/to/.npm/_logs/2020-11-03T22_41_51_438Z-debug.log
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To fix this, we can add an ESLint plugin that knows about the Cypress globals:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;eslint-plugin-cypress

added&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;package,&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;audited&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1621&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;4s

&lt;span class="m"&gt;251&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;looking&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;funding
&lt;span class="w"&gt;  &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;fund&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details

&lt;span class="m"&gt;74&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vulnerabilities&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;moderate,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;high&lt;span class="o"&gt;)&lt;/span&gt;

To&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;issues&lt;span class="w"&gt; &lt;/span&gt;that&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;require&lt;span class="w"&gt; &lt;/span&gt;attention,&lt;span class="w"&gt; &lt;/span&gt;run:
&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;audit&lt;span class="w"&gt; &lt;/span&gt;fix

To&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;issues&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;including&lt;span class="w"&gt; &lt;/span&gt;breaking&lt;span class="w"&gt; &lt;/span&gt;changes&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;run:
&lt;span class="w"&gt;  &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;audit&lt;span class="w"&gt; &lt;/span&gt;fix&lt;span class="w"&gt; &lt;/span&gt;--force

Run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;audit&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We could just add this plugin at the top level, but it's better to be specific - &lt;code&gt;cy&lt;/code&gt; will only be in scope for the files in the &lt;code&gt;cypress/&lt;/code&gt; directory, so we can set an ESLint &lt;em&gt;override&lt;/em&gt; for those files in &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;eslintConfig&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;extends&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;react-app&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;react-app/jest&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;overrides&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;extends&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;plugin:cypress/recommended&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;files&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cypress/**/*.js&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now &lt;code&gt;npm run lint&lt;/code&gt; should be fine.&lt;/p&gt;</content><category term="development"></category><category term="javascript"></category><category term="tdd"></category><category term="xp"></category></entry><entry><title>Runtime configuration for single-page apps</title><link href="https://blog.jonrshar.pe/2020/Sep/19/spa-config.html" rel="alternate"></link><published>2020-09-19T15:15:00+01:00</published><updated>2021-03-21T20:00:00+00:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2020-09-19:/2020/Sep/19/spa-config.html</id><summary type="html">&lt;p&gt;Tips and tricks for deploying JavaScript SPAs with runtime configuration&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Setting the scene&lt;/h2&gt;
&lt;p&gt;Let's imagine the following scenario: you are working on a single-page app (SPA) that's part of a larger system. Could be Angular, React, Svelte, Vue, ... that doesn't really matter. What &lt;em&gt;does&lt;/em&gt; matter is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;that it builds to HTML, CSS and JS; &lt;/li&gt;
&lt;li&gt;that those assets are deployed statically (i.e. &lt;em&gt;not&lt;/em&gt; serving them from the same app that provides the backend APIs or using SSR to generate them at runtime, so you don't have access to environment variables);&lt;/li&gt;
&lt;li&gt;that you're deploying to multiple different environments (e.g. acceptance, staging and production); and&lt;/li&gt;
&lt;li&gt;that it needs some kind of configuration (e.g. URLs for relevant backend services) to run correctly that varies across the different environments.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is quite a common problem, but it has tripped up a few teams I've worked with. The typical approach to per-environment configuration, setting environment variables that the app can access, isn't effective here. Because the assets are static files and the deployment environment is just a basic web server, it's not clear how to get the content from those environment variables into the assets. The SPA itself is actually running in your client's browser, which doesn't have access to the server-side environment at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: because I work for &lt;a href="https://tanzu.vmware.com/labs"&gt;VMware Tanzu Labs&lt;/a&gt; and this is the technology I'm most familiar with using for these apps, the practical examples will be based around deployment to the &lt;a href="https://docs.cloudfoundry.org/buildpacks/staticfile/index.html"&gt;staticfile buildpack&lt;/a&gt; on &lt;a href="https://tanzu.vmware.com/application-service"&gt;Tanzu Application Service&lt;/a&gt; (TAS). However, the patterns can be applied to whatever deployment environment you're using.&lt;/p&gt;
&lt;h2&gt;Build-time configuration&lt;/h2&gt;
&lt;p&gt;One pattern that's used in e.g. React &lt;a href="https://create-react-app.dev/docs/adding-custom-environment-variables/"&gt;custom environment variables&lt;/a&gt; (using Webpack's &lt;a href="https://webpack.js.org/plugins/define-plugin/"&gt;&lt;code&gt;DefinePlugin&lt;/code&gt;&lt;/a&gt;) and Angular &lt;a href="https://angular.io/guide/build#configuring-application-environments"&gt;application environments&lt;/a&gt; is injecting the configuration at &lt;em&gt;build&lt;/em&gt; time. The appropriate settings are taken from the environment or specific files and baked in when you create the static assets. This means that you have multiple &lt;em&gt;different&lt;/em&gt; sets of assets per commit, one for each environment you need to deploy to. You have two choices:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;These are all built at the same time from the same versions of the dependencies and stored in an artifact repository. This means holding and managing multiple (mostly identical) copies, some of which may never actually get used. But storage is cheap and the alternative is...&lt;/li&gt;
&lt;li&gt;They are built as needed, i.e. the staging build is only created when a specific commit is identified for review. This puts a lot of pressure on a reproducible build process, with all dependencies locked and still available (in the Node ecosystem &lt;code&gt;package-lock.json&lt;/code&gt; and &lt;code&gt;npm ci&lt;/code&gt; can recreate the same dependency tree, unless something got &lt;a href="https://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/"&gt;unpublished&lt;/a&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Either way, any issues in the build process could mean that one or more of the builds doesn't work correctly and, as you're not actually testing the same asset, you &lt;strong&gt;only find out when that specific build is deployed&lt;/strong&gt;. This is not where you want to be for a modern, &lt;a href="https://tanzu.vmware.com/content/blog/beyond-the-twelve-factor-app"&gt;12+ factor&lt;/a&gt; application. Oh, and even if you choose approach 1, introducing a new environment you need to deploy to would automatically push you back to approach 2, needing to recreate an existing build with a new set of configuration.&lt;/p&gt;
&lt;h2&gt;Runtime configuration&lt;/h2&gt;
&lt;p&gt;Much better than baking in the configuration when you build the assets is to be able to inject it at runtime. One method that I've used in various guises is extracting the app's configuration to one specific file. From there you have various options for switching configuration between environments, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Copy across an environment-specific file over the default; or&lt;/li&gt;
&lt;li&gt;Generate a new file from a template and e.g. environment variables (see &lt;a href="https://timysewyn.be/blog/2017-12-27-Deploying-web-applications-with-environment-specific-configurations/"&gt;my colleague's post&lt;/a&gt; on how to do that with &lt;code&gt;envsubst&lt;/code&gt;, for example).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These methods are much simpler and therefore safer operations than rebuilding or transforming the source code itself; they can trivially be scripted as part of an automated deployment process. Broadly there are three ways to do this, outlined below.&lt;/p&gt;
&lt;h3&gt;JavaScript&lt;/h3&gt;
&lt;p&gt;Probably the most common way to extract configuration is to load a separate JavaScript file directly in your HTML (i.e. &lt;em&gt;not&lt;/em&gt; part of the bundle that e.g. Webpack is creating):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;config.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;then in the file you're loading, in this case &lt;code&gt;config.js&lt;/code&gt;, add whatever configuration you need to the global &lt;code&gt;window&lt;/code&gt; object so your app code can access it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;host.domain.ext&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;HTML&lt;/h3&gt;
&lt;p&gt;Server-side includes (SSI) are a way to dynamically inject content into the responses you're serving. So in your &lt;code&gt;index.html&lt;/code&gt; you would have an include directive:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;root&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="cm"&gt;&amp;lt;!--#include virtual=&amp;quot;globals.html&amp;quot; --&amp;gt;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;then in &lt;code&gt;globals.html&lt;/code&gt; have a script element that updates the &lt;code&gt;window&lt;/code&gt; object as above:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Production&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Configuring this in the buildpack is simple; you can just add &lt;code&gt;ssi: enabled&lt;/code&gt; to a &lt;code&gt;Staticfile&lt;/code&gt; at the root of the deployed directory, along with any other configuration (e.g. &lt;code&gt;pushstate: enabled&lt;/code&gt; to enable client-side routing using the history API).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; you'll have to make sure your build process leaves the directives in the HTML; I discovered while putting examples together for this post that Create React App &lt;a href="https://github.com/facebook/create-react-app/issues/4245"&gt;stripped comments out&lt;/a&gt;, for example, so you need to make sure you're using version 5.1.0 or newer of &lt;code&gt;html-minifier-terser&lt;/code&gt; where these directives are ignored by default (see this &lt;a href="https://github.com/DanielRuf/html-minifier-terser/pull/39"&gt;Pull Request&lt;/a&gt;).&lt;/p&gt;
&lt;h3&gt;JSON&lt;/h3&gt;
&lt;p&gt;A third option is having a JSON file, and making a request for it when the app starts up. Rather than the browser making the request for you, as with the JavaScript options, this is made explicitly from the app itself. Once the request resolves the configuration data can be added to the &lt;code&gt;window&lt;/code&gt; as above.&lt;/p&gt;
&lt;p&gt;The downside of this is that you need to wait for a request to finish &lt;em&gt;in the app runtime&lt;/em&gt; before the configuration is available. Some frameworks can help you with this; for example, Angular provides &lt;a href="https://angular.io/api/core/APP_INITIALIZER"&gt;&lt;code&gt;APP_INITIALIZER&lt;/code&gt;&lt;/a&gt;, a hook that allows you to delay initial loading of your app code until the promises you supply have been resolved.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;@Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ConfigurationService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;initialise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="ow"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Configuration&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/config.json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toPromise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ConfigService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;APP_INITIALIZER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;useFactory&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ConfigurationService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialise&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;deps&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ConfigurationService&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;multi&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In React, this is the sort of thing that the experimental &lt;a href="https://reactjs.org/docs/concurrent-mode-suspense.html"&gt;suspense&lt;/a&gt; API looks like it will be really useful for.&lt;/p&gt;
&lt;h3&gt;Accessing configuration&lt;/h3&gt;
&lt;p&gt;However you're loading the configuration, you don't really want your components coupled to the &lt;code&gt;window&lt;/code&gt; (this makes e.g. testing harder), so rather than having &lt;code&gt;window.configuration&lt;/code&gt; accessed all over your app I would recommend having a single &lt;code&gt;configuration.js&lt;/code&gt; containing something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Development&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and using &lt;code&gt;import configuration from "path/to/configuration";&lt;/code&gt; to access it in your other JavaScript files.&lt;/p&gt;
&lt;p&gt;This is slightly more complicated when the additional configuration is loaded asynchronously (i.e. using the JSON method). The &lt;code&gt;||&lt;/code&gt; in the file would only be evaluated once, which may be &lt;em&gt;before&lt;/em&gt; the non-default configuration has been loaded. One method to ensure the app always finds up-to-date configuration is to use a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy"&gt;&lt;code&gt;Proxy&lt;/code&gt;&lt;/a&gt; to ensure the latest &lt;code&gt;window.configuration&lt;/code&gt; is checked for on every access:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Proxy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Development&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Trade-offs&lt;/h3&gt;
&lt;p&gt;Which of these methods you choose will depend on your specific context, I've summarised some of the pros and cons I thought of below:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Method&lt;/th&gt;
      &lt;th&gt;Pros&lt;/th&gt;
      &lt;th&gt;Cons&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;JavaScript&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;
        &lt;ul&gt;
          &lt;li&gt;No additional configuration needed&lt;/li&gt;
          &lt;li&gt; JavaScript can include dynamic values if needed&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/td&gt;
      &lt;td&gt;
        &lt;ul&gt;
          &lt;li&gt;Another round trip to the server&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;HTML&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;
        &lt;ul&gt;
          &lt;li&gt;Guaranteed to be available when the app loads, no additional round trips&lt;/li&gt;
          &lt;li&gt;JavaScript can include dynamic values if needed&lt;/li&gt;
          &lt;li&gt;Works for arbitrary content (e.g. tracking pixels)&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/td&gt;
      &lt;td&gt;
        &lt;ul&gt;
          &lt;li&gt;Requires buildpack/server configuration&lt;/li&gt;
          &lt;li&gt;Processing overhead on requests (only for the &lt;code&gt;text/html&lt;/code&gt; MIME type by default in NGINX)&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;JSON&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;
        &lt;ul&gt;
          &lt;li&gt;No additional configuration needed&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/td&gt;
      &lt;td&gt;
        &lt;ul&gt;
          &lt;li&gt;Adds complexity to the app to handle asynchronous access to configuration&lt;/li&gt;
          &lt;li&gt;Another round trip to the server&lt;/li&gt;
          &lt;li&gt;Limited to static JSON content&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; that although I've classed dynamic value support as a Pro above, the flexibility of arbitrary JS code (loaded via HTML or JS files) vs. static JSON data also introduces a security risk. Use with caution! &lt;/p&gt;
&lt;p&gt;I've created simple examples in various frameworks to show how these ideas can be applied practically, they're all published in &lt;a href="https://github.com/spa-configuration"&gt;this GitHub org&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Path routing&lt;/h2&gt;
&lt;p&gt;If you're using a managed deployment platform that handles routing for you, or can set up routing using something like &lt;a href="https://spring.io/projects/spring-cloud-gateway"&gt;Spring Cloud Gateway&lt;/a&gt;, you can send traffic to different apps depending on the path. This means you don't need to configure the client the different service APIs at all (or need to configure CORS on the server), because the requests get automagically routed for you.&lt;/p&gt;
&lt;p&gt;For example, I've used this in TAS to set up an Angular frontend served by NGINX on &lt;code&gt;host.domain.ext&lt;/code&gt;, with a Spring Boot backend on &lt;code&gt;host.domain.ext/api&lt;/code&gt; so that the frontend can make relative requests (i.e. to &lt;code&gt;"/api/endpoint"&lt;/code&gt; rather than &lt;code&gt;"host.domain.ext/api/endpoint"&lt;/code&gt;), even though they're still &lt;em&gt;two separate apps&lt;/em&gt;. You can even set this up from a single manifest file, if you're working in a monorepo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="nt"&gt;applications&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;frontend&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;host.domain.ext&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;buildpacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;staticfile_buildpack&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;...&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;backend&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;host.domain.ext/api&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The configuration for this is covered &lt;a href="https://docs.cloudfoundry.org/devguide/deploy-apps/routes-domains.html#-create-an-http-route-with-a-path"&gt;here&lt;/a&gt;. &lt;strong&gt;Note&lt;/strong&gt; that the &lt;code&gt;path&lt;/code&gt; key in the manifest file is the directory within your repo that the app is in, &lt;em&gt;not&lt;/em&gt; the network path to host the app at.&lt;/p&gt;
&lt;p&gt;The TAS router is smart enough to allow push-state routing, too; a request to &lt;code&gt;host.domain.ext/foo&lt;/code&gt; will go to the frontend app unless you explicitly mount another app at &lt;code&gt;/foo&lt;/code&gt;. Between that and the ability to simply set &lt;code&gt;pushstate: enabled&lt;/code&gt; in the &lt;code&gt;Staticfile&lt;/code&gt;, this is a really easy way to handle client-side routing with something like &lt;a href="https://github.com/ReactTraining/react-router"&gt;React Router&lt;/a&gt; or the built-in &lt;a href="https://angular.io/guide/router"&gt;Angular router&lt;/a&gt;, taking full advantage of the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/History"&gt;HTML5 history API&lt;/a&gt;. The buildpack will configure NGINX to handle serving the &lt;code&gt;index.html&lt;/code&gt; for any missing requests and your SPA can take over from there.&lt;/p&gt;
&lt;p&gt;One limitation of the path routing approach is that this &lt;em&gt;only&lt;/em&gt; removes the configuration issue for API URLs; if you have other configuration that varies by environment you'll need to manage that using the methods above. However, if you can make a relative request and have that routed to a dynamic application, the JSON approach can be used and the request fulfilled based on environment variables, so you don't need to swap out files between environments.&lt;/p&gt;</content><category term="development"></category><category term="xp"></category><category term="ci"></category><category term="angular"></category><category term="react"></category></entry><entry><title>JS TDD FTW</title><link href="https://blog.jonrshar.pe/2020/Aug/31/js-tdd-ftw.html" rel="alternate"></link><published>2020-08-31T16:00:00+01:00</published><updated>2024-08-17T11:30:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2020-08-31:/2020/Aug/31/js-tdd-ftw.html</id><summary type="html">&lt;p&gt;Test-driven JavaScript development done right - part 1&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: a &lt;a href="https://blog.jonrshar.pe/2024/Apr/09/js-tdd-ftw-redux.html"&gt;newer &lt;em&gt;"redux"&lt;/em&gt; version&lt;/a&gt; of this article, using the Node.js test runner, has been published.
You can continue to work through this version if you'd prefer to use Jest.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the key Extreme Programming (&lt;a href="http://wiki.c2.com/?ExtremeProgramming"&gt;XP&lt;/a&gt;) engineering practices is test-driven development (TDD), usually expressed as repeatedly following this simple, three-step process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Red&lt;/strong&gt; - write a failing test that describes the behaviour you want;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Green&lt;/strong&gt; - write the simplest possible code to make the test pass; and&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refactor&lt;/strong&gt; - clean up your code without breaking the tests.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I was recently asked if I knew of a good TDD intro for people who were comfortable with JavaScript but hadn't done much testing, so I did some research. There are lots of examples of testing and TDD out there, but often: tied to specific frameworks (e.g. React); with unclear prerequisites; and even showing poor testing practices. So below I'm going to give a proper example of vanilla JavaScript TDD done &lt;em&gt;"the right way"&lt;/em&gt;, sprinkling some bonus command line and git practice throughout.&lt;/p&gt;
&lt;h3&gt;Requirements&lt;/h3&gt;
&lt;p&gt;I've aimed this content at more junior developers, so there are more explanations than all readers will need, but anyone new to testing and TDD should find something to take from it. We'll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;*nix command line: already provided on macOS and Linux; if you're using Windows try &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/about"&gt;WSL&lt;/a&gt; or &lt;a href="https://gitforwindows.org/"&gt;Git BASH&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/"&gt;Node&lt;/a&gt; (10+ recommended, Jest 26 &lt;a href="https://jestjs.io/blog/2020/05/05/jest-26#other-breaking-changes-in-jest-26"&gt;dropped support&lt;/a&gt; for Node 8; run &lt;code&gt;node -v&lt;/code&gt; to check) and NPM; and&lt;/li&gt;
&lt;li&gt;Familiarity with ES6 JavaScript syntax (specifically arrow functions).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We also need something to implement. &lt;a href="https://en.wikipedia.org/wiki/Rock_paper_scissors"&gt;Rock Paper Scissors&lt;/a&gt; (or just RPS) is a simple playground game that takes some inputs (the shapes that the players present) and gives a single output (the outcome), which makes it a good fit for a simple function to test drive. If you're not familiar with the rules, read the linked Wikipedia article before continuing.&lt;/p&gt;
&lt;p&gt;Before we get into the TDD process, think about what the code for an implementation of RPS might look like. Don't write any code yet (we don't have the failing tests to make us do that!) but imagine a function - what parameters would it accept? What would it return? We're expecting different outputs for different inputs, which implies some conditional logic - what conditions do you think would be involved? Note your ideas down, we'll revisit them later.&lt;/p&gt;
&lt;p&gt;As we go through, please carefully &lt;em&gt;read everything&lt;/em&gt;. I'd recommend &lt;em&gt;typing the code&lt;/em&gt; rather than copy-pasting, especially if you're a new developer; it's good practice to build your muscle memory.&lt;/p&gt;
&lt;h2&gt;Setup [1/10]&lt;/h2&gt;
&lt;p&gt;Let's get up and running. Starting in your working directory (e.g. I use &lt;code&gt;~/workspace&lt;/code&gt;), run the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;rps-tdd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;--allow-empty&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Initial commit&amp;#39;&lt;/span&gt;
Initialized&lt;span class="w"&gt; &lt;/span&gt;empty&lt;span class="w"&gt; &lt;/span&gt;Git&lt;span class="w"&gt; &lt;/span&gt;repository&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd/.git/
&lt;span class="o"&gt;[&lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;root-commit&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Initial&lt;span class="w"&gt; &lt;/span&gt;commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;By chaining multiple commands using &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; (&lt;em&gt;"and"&lt;/em&gt;, assuming the previous commands all succeeded), this will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a new directory named &lt;code&gt;rps-tdd/&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Switch into it (&lt;code&gt;$_&lt;/code&gt; references the argument to the last command, see e.g. &lt;a href="https://stackoverflow.com/q/30154694/3001761"&gt;this SO question&lt;/a&gt;);&lt;/li&gt;
&lt;li&gt;Initialise a new git repository; and&lt;/li&gt;
&lt;li&gt;Create an empty initial commit (&lt;code&gt;--allow-empty&lt;/code&gt; lets us create this first commit without having any content, and &lt;code&gt;-m&lt;/code&gt; lets us supply the commit message on the command line).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now we need a basic Node project:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;package.json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Create NPM package&amp;#39;&lt;/span&gt;
Wrote&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd/package.json:

&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rps-tdd&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;version&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1.0.0&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;main&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;index.js&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scripts&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;keywords&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;author&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;license&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ISC&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="o"&gt;[&lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Create&lt;span class="w"&gt; &lt;/span&gt;NPM&lt;span class="w"&gt; &lt;/span&gt;package
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;package.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creates a basic &lt;code&gt;package.json&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Adds that to our repo; and&lt;/li&gt;
&lt;li&gt;Makes another commit.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next, we're going to need something to &lt;em&gt;run&lt;/em&gt; our tests. There are loads of options here (I've used &lt;a href="https://jasmine.github.io/"&gt;Jasmine&lt;/a&gt;, &lt;a href="https://mochajs.org/"&gt;Mocha&lt;/a&gt;, &lt;a href="https://www.npmjs.com/package/tape"&gt;tape&lt;/a&gt;, ...) but we're going to start with one that's become very popular recently: &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt;. Let's install it as a &lt;em&gt;development dependency&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--save-dev&lt;span class="w"&gt; &lt;/span&gt;jest
npm&lt;span class="w"&gt; &lt;/span&gt;WARN&lt;span class="w"&gt; &lt;/span&gt;deprecated&lt;span class="w"&gt; &lt;/span&gt;request@2.88.2:&lt;span class="w"&gt; &lt;/span&gt;request&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;been&lt;span class="w"&gt; &lt;/span&gt;deprecated,&lt;span class="w"&gt; &lt;/span&gt;see&lt;span class="w"&gt; &lt;/span&gt;https://github.com/request/request/issues/3142
npm&lt;span class="w"&gt; &lt;/span&gt;WARN&lt;span class="w"&gt; &lt;/span&gt;deprecated&lt;span class="w"&gt; &lt;/span&gt;request-promise-native@1.0.9:&lt;span class="w"&gt; &lt;/span&gt;request-promise-native&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;been&lt;span class="w"&gt; &lt;/span&gt;deprecated&lt;span class="w"&gt; &lt;/span&gt;because&lt;span class="w"&gt; &lt;/span&gt;it&lt;span class="w"&gt; &lt;/span&gt;extends&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;now&lt;span class="w"&gt; &lt;/span&gt;deprecated&lt;span class="w"&gt; &lt;/span&gt;request&lt;span class="w"&gt; &lt;/span&gt;package,&lt;span class="w"&gt; &lt;/span&gt;see&lt;span class="w"&gt; &lt;/span&gt;https://github.com/request/request/issues/3142
npm&lt;span class="w"&gt; &lt;/span&gt;WARN&lt;span class="w"&gt; &lt;/span&gt;deprecated&lt;span class="w"&gt; &lt;/span&gt;har-validator@5.1.5:&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;library&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;no&lt;span class="w"&gt; &lt;/span&gt;longer&lt;span class="w"&gt; &lt;/span&gt;supported
npm&lt;span class="w"&gt; &lt;/span&gt;notice&lt;span class="w"&gt; &lt;/span&gt;created&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;lockfile&lt;span class="w"&gt; &lt;/span&gt;as&lt;span class="w"&gt; &lt;/span&gt;package-lock.json.&lt;span class="w"&gt; &lt;/span&gt;You&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;file.
npm&lt;span class="w"&gt; &lt;/span&gt;WARN&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@1.0.0&lt;span class="w"&gt; &lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;description
npm&lt;span class="w"&gt; &lt;/span&gt;WARN&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@1.0.0&lt;span class="w"&gt; &lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;repository&lt;span class="w"&gt; &lt;/span&gt;field.

+&lt;span class="w"&gt; &lt;/span&gt;jest@26.4.2
added&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;506&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;347&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;contributors&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;audited&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;506&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;.837s

&lt;span class="m"&gt;21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;looking&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;funding
&lt;span class="w"&gt;  &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;fund&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details

found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vulnerabilities
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's quite a lot of information, most of which isn't relevant to us right now - if the install succeeded (you can run &lt;code&gt;echo $?&lt;/code&gt; and should see the "success" exit code of &lt;code&gt;0&lt;/code&gt;, if you're unsure), we can move on. If you really want to know what the other details mean, see the end of this post.&lt;/p&gt;
&lt;p&gt;We should update our repo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;status
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;master
Changes&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;staged&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;commit:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git add &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;what&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git restore &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;discard&lt;span class="w"&gt; &lt;/span&gt;changes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;working&lt;span class="w"&gt; &lt;/span&gt;directory&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;modified:&lt;span class="w"&gt;   &lt;/span&gt;package.json

Untracked&lt;span class="w"&gt; &lt;/span&gt;files:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git add &amp;lt;file&amp;gt;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;include&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;what&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;committed&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;node_modules/
&lt;span class="w"&gt;    &lt;/span&gt;package-lock.json

no&lt;span class="w"&gt; &lt;/span&gt;changes&lt;span class="w"&gt; &lt;/span&gt;added&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git add&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;and/or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git commit -a&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So we've changed &lt;code&gt;package.json&lt;/code&gt;, created the &lt;code&gt;node_modules/&lt;/code&gt; dependencies directory and added &lt;code&gt;package-lock.json&lt;/code&gt;. Typically we don't want all of the dependencies in our source control, so let's:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ignore the dependencies directory by adding it to a &lt;code&gt;.gitignore&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Add all of the rest (&lt;code&gt;git add .&lt;/code&gt; means &lt;em&gt;"add everything from this directory and below"&lt;/em&gt;, so will add both &lt;code&gt;.gitignore&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt; as well as the changes to &lt;code&gt;package.json&lt;/code&gt;); and&lt;/li&gt;
&lt;li&gt;Commit the result.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;node_modules/&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;.gitignore&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Install Jest&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Install&lt;span class="w"&gt; &lt;/span&gt;Jest
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4680&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletion&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;.gitignore
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;package-lock.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Running Jest [2/10]&lt;/h2&gt;
&lt;p&gt;Now we can update &lt;code&gt;package.json&lt;/code&gt; to set Jest to be our test command. By default, NPM creates a test script that will throw an error, as we saw above:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you run this using &lt;code&gt;npm test&lt;/code&gt; (alternatively &lt;code&gt;npm run test&lt;/code&gt; or even just &lt;code&gt;npm t&lt;/code&gt;) you see the result:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error: no test specified&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;

Error:&lt;span class="w"&gt; &lt;/span&gt;no&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;specified
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;Test&lt;span class="w"&gt; &lt;/span&gt;failed.&lt;span class="w"&gt;  &lt;/span&gt;See&lt;span class="w"&gt; &lt;/span&gt;above&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;details.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Edit &lt;code&gt;package.json&lt;/code&gt; to update the script to &lt;code&gt;"test": "jest"&lt;/code&gt;, using an editor or IDE of your choice, to use the test framework we just installed. Then run the tests again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest

No&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;found,&lt;span class="w"&gt; &lt;/span&gt;exiting&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;code&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
Run&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;--passWithNoTests&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;code&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
In&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;checked.
&lt;span class="w"&gt;  &lt;/span&gt;testMatch:&lt;span class="w"&gt; &lt;/span&gt;**/__tests__/**/*.&lt;span class="o"&gt;[&lt;/span&gt;jt&lt;span class="o"&gt;]&lt;/span&gt;s?&lt;span class="o"&gt;(&lt;/span&gt;x&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;**/?&lt;span class="o"&gt;(&lt;/span&gt;*.&lt;span class="o"&gt;)&lt;/span&gt;+&lt;span class="o"&gt;(&lt;/span&gt;spec&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.&lt;span class="o"&gt;[&lt;/span&gt;tj&lt;span class="o"&gt;]&lt;/span&gt;s?&lt;span class="o"&gt;(&lt;/span&gt;x&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;matches
&lt;span class="w"&gt;  &lt;/span&gt;testPathIgnorePatterns:&lt;span class="w"&gt; &lt;/span&gt;/node_modules/&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;matches
&lt;span class="w"&gt;  &lt;/span&gt;testRegex:&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;matches
Pattern:&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;matches
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;Test&lt;span class="w"&gt; &lt;/span&gt;failed.&lt;span class="w"&gt;  &lt;/span&gt;See&lt;span class="w"&gt; &lt;/span&gt;above&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;details.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This seems unhappy, but reading the output we can see why: &lt;code&gt;No tests found&lt;/code&gt;. One option given there is to add the &lt;code&gt;--passWithNoTests&lt;/code&gt; flag, but maybe we should write a test instead. Let's start with something completely trivial to make sure everything is working; add the following to a file named &lt;code&gt;index.test.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should work&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now run the tests a third time:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest

&lt;span class="w"&gt; &lt;/span&gt;PASS&lt;span class="w"&gt;  &lt;/span&gt;./index.test.js
&lt;span class="w"&gt;  &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;work&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.224&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Much happier! So what does that test &lt;em&gt;do&lt;/em&gt;, what's going on there?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We call the &lt;code&gt;it&lt;/code&gt; function (provided by Jest, you can also use the name &lt;code&gt;test&lt;/code&gt;) to &lt;strong&gt;register a test&lt;/strong&gt;. We pass it two things:&lt;ul&gt;
&lt;li&gt;The name of the test, as a string (you can see this name in the output, too). In this style of testing we use the function name along with the test as one sentence describing our expectation: &lt;em&gt;"it should work"&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;The body of the test, as a function. Right now we're just &lt;em&gt;registering&lt;/em&gt; the test, Jest will call that function for us when it runs the test.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Within the test body, we &lt;strong&gt;establish our expectations&lt;/strong&gt;. What exactly do we think should happen? I've split this into three sections:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Arrange&lt;/strong&gt; (sometimes known as &lt;em&gt;"given"&lt;/em&gt;) - set up the preconditions for our test, in this case two initial values.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Act&lt;/strong&gt; (or &lt;em&gt;"when"&lt;/em&gt;) - do some work, in this case adding them together. This is what we're actually testing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assert&lt;/strong&gt; (or &lt;em&gt;"then"&lt;/em&gt;) - make sure that the work was done correctly. The &lt;code&gt;expect&lt;/code&gt; function is also provided by Jest; it takes the value we want to check and returns an object with a lot of helpful &lt;a href="https://jestjs.io/docs/en/expect"&gt;matcher methods&lt;/a&gt; to describe our expectations of it. Again the naming convention allows us to write out a simple sentence: &lt;em&gt;"expect result to be 3"&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One of the things Jest does really well is test feedback. If we had an inaccurate expectation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should work&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;it would tell us exactly what the problem was:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest

&lt;span class="w"&gt; &lt;/span&gt;FAIL&lt;span class="w"&gt;  &lt;/span&gt;./index.test.js
&lt;span class="w"&gt;  &lt;/span&gt;✕&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;work&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;work

&lt;span class="w"&gt;    &lt;/span&gt;expect&lt;span class="o"&gt;(&lt;/span&gt;received&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;expected&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;Object.is&lt;span class="w"&gt; &lt;/span&gt;equality

&lt;span class="w"&gt;    &lt;/span&gt;Expected:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;Received:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;

&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;const&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;expect&lt;span class="o"&gt;(&lt;/span&gt;result&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;^
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;index.test.js:7:18&lt;span class="o"&gt;)&lt;/span&gt;

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.979&lt;span class="w"&gt; &lt;/span&gt;s,&lt;span class="w"&gt; &lt;/span&gt;estimated&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;Test&lt;span class="w"&gt; &lt;/span&gt;failed.&lt;span class="w"&gt;  &lt;/span&gt;See&lt;span class="w"&gt; &lt;/span&gt;above&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;details.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Protip&lt;/strong&gt;: always read the outputs carefully! Sometimes a test fails for an unexpected reason, which usually tells you something interesting.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, we're happy things are working so far.&lt;/p&gt;
&lt;h2&gt;A failing test [3/10]&lt;/h2&gt;
&lt;p&gt;Let's start some actual TDD, and write our first failing test. Replace the content of &lt;code&gt;index.test.js&lt;/code&gt; with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock, paper, scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should say left wins for rock vs. scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note I've introduced another Jest function, &lt;code&gt;describe&lt;/code&gt;. This registers a &lt;em&gt;group&lt;/em&gt; of tests, usually referred to as a &lt;em&gt;"suite"&lt;/em&gt;. Like &lt;code&gt;it&lt;/code&gt;/&lt;code&gt;test&lt;/code&gt; it takes a name and a function, then our individual tests are registered inside that function.&lt;/p&gt;
&lt;p&gt;Our first test is that, given that &lt;code&gt;left&lt;/code&gt; is &lt;code&gt;"rock"&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt; is &lt;code&gt;"scissors"&lt;/code&gt; (&lt;em&gt;"Arrange"&lt;/em&gt;), when the shapes are compared (&lt;em&gt;"Act"&lt;/em&gt;) , then the winner should be &lt;code&gt;"left"&lt;/code&gt; (&lt;em&gt;"Assert"&lt;/em&gt;) because rock blunts scissors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; one key benefit of TDD here - we can try out how we should interact with our code (its &lt;em&gt;"interface"&lt;/em&gt;) before we've even written any. Maybe it should return something other than a string, for example? We can have that discussion now, while it's just a matter of changing our minds rather than the code.&lt;/p&gt;
&lt;p&gt;Before we run the first test, &lt;a href="https://markhneedham.com/blog/2010/07/28/tdd-call-your-shots/"&gt;&lt;em&gt;"call the shot"&lt;/em&gt;&lt;/a&gt; - make a prediction of what the test result will be, pass or fail. If you think the test will fail, &lt;strong&gt;why&lt;/strong&gt;; will the &lt;code&gt;expect&lt;/code&gt;ation be unmet (and what value do you think you'll get instead) or will something else go wrong? This is really good practice for &lt;em&gt;"playing computer"&lt;/em&gt; (modelling the behaviour of the code in your head) and you can write your guess down (or say it out loud if you're pairing) to keep yourself honest. Now let's run it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tdd&lt;/span&gt;&lt;span class="mf"&gt;@1.0.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tdd&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jest&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FAIL&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;✕&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;●&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;›&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;ReferenceError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;defined&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;anonymous&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Suites&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="nl"&gt;Tests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="nl"&gt;Snapshots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="nl"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mf"&gt;1.226&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="n"&gt;Ran&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;suites&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ERR&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;See&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;above&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;more&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;...were you right?&lt;/p&gt;
&lt;h2&gt;The simplest possible change [4/10]&lt;/h2&gt;
&lt;p&gt;As you may have guessed, this fails because &lt;code&gt;rps&lt;/code&gt; &lt;em&gt;doesn't exist yet&lt;/em&gt;. Let's make the simplest possible change that will at least change the error we're receiving; define the function. At this stage we could &lt;code&gt;import&lt;/code&gt;/&lt;code&gt;require&lt;/code&gt; the function from another file, but let's keep things simple for now; add the following to the top of &lt;code&gt;index.test.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest

&lt;span class="w"&gt; &lt;/span&gt;FAIL&lt;span class="w"&gt;  &lt;/span&gt;./index.test.js
&lt;span class="w"&gt;  &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt;    &lt;/span&gt;✕&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;›&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors

&lt;span class="w"&gt;    &lt;/span&gt;expect&lt;span class="o"&gt;(&lt;/span&gt;received&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;expected&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;Object.is&lt;span class="w"&gt; &lt;/span&gt;equality

&lt;span class="w"&gt;    &lt;/span&gt;Expected:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;Received:&lt;span class="w"&gt; &lt;/span&gt;undefined

&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;const&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;outcome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rps&lt;span class="o"&gt;(&lt;/span&gt;left,&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;expect&lt;span class="o"&gt;(&lt;/span&gt;outcome&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                     &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;index.test.js:10:21&lt;span class="o"&gt;)&lt;/span&gt;

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.196&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;Test&lt;span class="w"&gt; &lt;/span&gt;failed.&lt;span class="w"&gt;  &lt;/span&gt;See&lt;span class="w"&gt; &lt;/span&gt;above&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;details.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Our test still doesn't pass, but at least we've changed the error message - we're now reaching the actual expectation, instead of crashing when we try to call the function. So let's make the simplest possible change that should get this passing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tdd&lt;/span&gt;&lt;span class="mf"&gt;@1.0.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tdd&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jest&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PASS&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;✓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Suites&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="nl"&gt;Tests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="nl"&gt;Snapshots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="nl"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mf"&gt;1.198&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="n"&gt;Ran&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;suites&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Great! This calls for a celebratory commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;First test - rock vs. scissors&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;First&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletion&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100644&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;index.test.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; another key benefit of TDD here - it tells you when you're done. Once the tests are passing, the implementation meets the current requirements.&lt;/p&gt;
&lt;h2&gt;The difficult second test [5/10]&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;"But wait"&lt;/em&gt;, you might be thinking, &lt;em&gt;"that's pointless, it doesn't &lt;strong&gt;do&lt;/strong&gt; anything!"&lt;/em&gt; And to an extent, that's true; our function just returns a hard-coded string. But let's think about what else has happened:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We've decided on an interface for our function, what it's going to receive and return;&lt;/li&gt;
&lt;li&gt;We've proved out a test setup that lets us make assertions on the behaviour of that function; and&lt;/li&gt;
&lt;li&gt;We've created the simplest possible implementation for the requirements we've expressed through tests so far, making our code very robust.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So let's build on that foundation; flip the shapes around to change the output so we can expect the test to fail. Add the following into the &lt;code&gt;describe&lt;/code&gt; callback in &lt;code&gt;index.test.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should say right wins for scissors vs. rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that the &lt;em&gt;"Act"&lt;/em&gt; is the same, but the &lt;em&gt;"Arrange"&lt;/em&gt; and &lt;em&gt;"Assert"&lt;/em&gt; have changed. Call the shot, then run the test again to see if you were correct:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest

&lt;span class="w"&gt; &lt;/span&gt;FAIL&lt;span class="w"&gt;  &lt;/span&gt;./index.test.js
&lt;span class="w"&gt;  &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;✕&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;›&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock

&lt;span class="w"&gt;    &lt;/span&gt;expect&lt;span class="o"&gt;(&lt;/span&gt;received&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;expected&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;Object.is&lt;span class="w"&gt; &lt;/span&gt;equality

&lt;span class="w"&gt;    &lt;/span&gt;Expected:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;Received:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;const&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rps&lt;span class="o"&gt;(&lt;/span&gt;left,&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;expect&lt;span class="o"&gt;(&lt;/span&gt;result&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;23&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;index.test.js:21:20&lt;span class="o"&gt;)&lt;/span&gt;

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.201&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;Test&lt;span class="w"&gt; &lt;/span&gt;failed.&lt;span class="w"&gt;  &lt;/span&gt;See&lt;span class="w"&gt; &lt;/span&gt;above&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;details.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Read that through carefully. What does it tell us?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Our first test is still passing. That's good news, we haven't broken anything!&lt;/li&gt;
&lt;li&gt;Our second test fails, because it returns &lt;code&gt;"left"&lt;/code&gt; but we want it to return &lt;code&gt;"right"&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, what's the simplest possible change that would get this test passing? Think about it for a minute or two.&lt;/p&gt;
&lt;p&gt;We're going to need some kind of &lt;em&gt;conditional logic&lt;/em&gt; here, because we return different results in different cases. However, we're supposed to be keeping things simple, so we don't want to leap all the way to a full implementation. How about this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest

&lt;span class="w"&gt; &lt;/span&gt;PASS&lt;span class="w"&gt;  &lt;/span&gt;./index.test.js
&lt;span class="w"&gt;  &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.216&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Alright, two down, let's commit what we've done so far:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Second test - scissors vs. rock&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;bd4bbd6&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Second&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We haven't created any new files since the last commit, so we can use the &lt;code&gt;-a&lt;/code&gt;/&lt;code&gt;--all&lt;/code&gt; flag to &lt;code&gt;git commit&lt;/code&gt; to include changes to all files, instead of needing to &lt;code&gt;git add&lt;/code&gt; anything.&lt;/p&gt;
&lt;h2&gt;Third time's the charm [6/10]&lt;/h2&gt;
&lt;p&gt;We've handled both of the cases involving rock and scissors, so let's try this one, scissors cut paper:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should say left wins for scissors vs. paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest

&lt;span class="w"&gt; &lt;/span&gt;FAIL&lt;span class="w"&gt;  &lt;/span&gt;./index.test.js
&lt;span class="w"&gt;  &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock
&lt;span class="w"&gt;    &lt;/span&gt;✕&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;›&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;paper

&lt;span class="w"&gt;    &lt;/span&gt;expect&lt;span class="o"&gt;(&lt;/span&gt;received&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;expected&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;Object.is&lt;span class="w"&gt; &lt;/span&gt;equality

&lt;span class="w"&gt;    &lt;/span&gt;Expected:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;Received:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;const&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rps&lt;span class="o"&gt;(&lt;/span&gt;left,&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;29&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;expect&lt;span class="o"&gt;(&lt;/span&gt;result&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;33&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;index.test.js:30:20&lt;span class="o"&gt;)&lt;/span&gt;

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.227&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;Test&lt;span class="w"&gt; &lt;/span&gt;failed.&lt;span class="w"&gt;  &lt;/span&gt;See&lt;span class="w"&gt; &lt;/span&gt;above&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;details.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So how can we get this to pass? We can no longer rely on the value of the first parameter alone, because we have two &lt;em&gt;different&lt;/em&gt; outputs where &lt;code&gt;left&lt;/code&gt; is &lt;code&gt;"scissors"&lt;/code&gt;, so we're going to have to also check the &lt;code&gt;right&lt;/code&gt; value. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tdd&lt;/span&gt;&lt;span class="mf"&gt;@1.0.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tdd&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jest&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PASS&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;✓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;✓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;✓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;

&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Suites&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="nl"&gt;Tests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="nl"&gt;Snapshots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="nl"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mf"&gt;1.109&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="n"&gt;Ran&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;suites&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's a successful outcome, but our code is a bit of a mess; a conditional expression inside another conditional expression isn't very clear and we've repeated the &lt;em&gt;"magic value"&lt;/em&gt; &lt;code&gt;"left"&lt;/code&gt; twice. So now we can &lt;strong&gt;refactor&lt;/strong&gt;, keep the tests passing but change the implementation. For example, how about:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tdd&lt;/span&gt;&lt;span class="mf"&gt;@1.0.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tdd&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jest&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PASS&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;✓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;✓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;✓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scissors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paper&lt;/span&gt;

&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Suites&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="nl"&gt;Tests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="nl"&gt;Snapshots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="nl"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mf"&gt;1.109&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="n"&gt;Ran&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;suites&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; a third key benefit of TDD here - we know that the code still does exactly what it's supposed to even though we've just changed the implementation. This allows us to confidently refactor towards cleaner code and higher quality.&lt;/p&gt;
&lt;p&gt;Let's treat ourselves to a commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Third test - scissors vs. paper&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Third&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;paper
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Are there any puns about four? [7/10]&lt;/h2&gt;
&lt;p&gt;Let's flip the last condition to cover the other case involving paper and scissors:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should say right wins for paper vs. scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest

&lt;span class="w"&gt; &lt;/span&gt;PASS&lt;span class="w"&gt;  &lt;/span&gt;./index.test.js
&lt;span class="w"&gt;  &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;paper
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.879&lt;span class="w"&gt; &lt;/span&gt;s,&lt;span class="w"&gt; &lt;/span&gt;estimated&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;...huh. &lt;code&gt;left&lt;/code&gt; isn't &lt;code&gt;"rock"&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt; isn't &lt;code&gt;"paper"&lt;/code&gt;, so it returns &lt;code&gt;"right"&lt;/code&gt;, which is the answer we wanted. This doesn't drive our implementation forward, but it is the behaviour we want, so let's commit this too:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Fourth test - paper vs. scissors&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Fourth&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Gift-wrapped rock [8/10]&lt;/h2&gt;
&lt;p&gt;At this point you can probably see what's coming next; paper wraps rock:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should say left wins for paper vs. rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, then run the test again to see if you were right:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;t

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@1.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/rps-tdd
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;jest

&lt;span class="w"&gt; &lt;/span&gt;FAIL&lt;span class="w"&gt;  &lt;/span&gt;./index.test.js
&lt;span class="w"&gt;  &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;paper
&lt;span class="w"&gt;    &lt;/span&gt;✓&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;✕&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;●&lt;span class="w"&gt; &lt;/span&gt;rock,&lt;span class="w"&gt; &lt;/span&gt;paper,&lt;span class="w"&gt; &lt;/span&gt;scissors&lt;span class="w"&gt; &lt;/span&gt;›&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;say&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;wins&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock

&lt;span class="w"&gt;    &lt;/span&gt;expect&lt;span class="o"&gt;(&lt;/span&gt;received&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;expected&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;Object.is&lt;span class="w"&gt; &lt;/span&gt;equality

&lt;span class="w"&gt;    &lt;/span&gt;Expected:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;Received:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;48&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;const&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rps&lt;span class="o"&gt;(&lt;/span&gt;left,&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;49&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;expect&lt;span class="o"&gt;(&lt;/span&gt;result&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;^
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;51&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;52&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;53&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;Object.&amp;lt;anonymous&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;index.test.js:50:20&lt;span class="o"&gt;)&lt;/span&gt;

Test&lt;span class="w"&gt; &lt;/span&gt;Suites:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Tests:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;passed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Snapshots:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total
Time:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.234&lt;span class="w"&gt; &lt;/span&gt;s
Ran&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;suites.
npm&lt;span class="w"&gt; &lt;/span&gt;ERR!&lt;span class="w"&gt; &lt;/span&gt;Test&lt;span class="w"&gt; &lt;/span&gt;failed.&lt;span class="w"&gt;  &lt;/span&gt;See&lt;span class="w"&gt; &lt;/span&gt;above&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;details.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Alright, this time we do get a failure again. Have a play with the implementation for a few minutes, see if you can come up with a way to write an implementation of the form &lt;code&gt;&amp;lt;condition&amp;gt; ? "left" : "right";&lt;/code&gt; that passes all five tests. Remember: write the code, call the shot, run the test, compare.&lt;/p&gt;
&lt;p&gt;For example, you might get to something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let's commit it and then flip to the last case:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Fifth test - paper vs. rock&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;bash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Fifth&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;paper&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;rock
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletion&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should say right wins for rock vs. paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we've reached a point where, however we try to rearrange it, we're &lt;em&gt;forced&lt;/em&gt; to be explicit about all of the cases. For example, we might write:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This probably looks a lot like what you imagined to begin with. Let's save it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Sixth test - rock vs. paper&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Sixth&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;rock&lt;span class="w"&gt; &lt;/span&gt;vs.&lt;span class="w"&gt; &lt;/span&gt;paper
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletion&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Draw! [9/10]&lt;/h2&gt;
&lt;p&gt;So far we've assumed the two participants choose different values. If you've played RPS, you'll know that's not always the case in real life - sometimes it's a draw.&lt;/p&gt;
&lt;p&gt;This brings us to the idea of &lt;em&gt;parameterised testing&lt;/em&gt; - generating tests based on canned data. Jest has built-in functionality to do this, named &lt;a href="https://jestjs.io/docs/en/api#testeachtablename-fn-timeout"&gt;&lt;code&gt;each&lt;/code&gt;&lt;/a&gt;, but it's often as easy to do it with an array and &lt;code&gt;forEach&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;both&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`should say draw for &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;both&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; vs. &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;both&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;both&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;both&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;draw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Call the shot, run the tests, review the output then, if all of that makes sense, complete the implementation. Maybe something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;draw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scissors&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paper&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once everything's passing and you're happy with your implementation, make the final commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Handle the draw cases&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;hash&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Handle&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;draw&lt;span class="w"&gt; &lt;/span&gt;cases
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changed,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's it! We've just test-driven an implementation of RPS, the right way. Reflect on the exercise - how does the implementation compare to what you'd initially imagined? What felt good or bad about the process?&lt;/p&gt;
&lt;p&gt;You can see my copy of this exercise at &lt;a href="https://github.com/textbook/rps-tdd"&gt;https://github.com/textbook/rps-tdd&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="exercises"&gt;Exercises [10/10]&lt;/h2&gt;
&lt;p&gt;Practice makes perfect! Here are some additional exercises you can run through:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Repeat the process, but tackle the pairs in a different order. What impact does the order have on how and when your implementation gains complexity? Do you end up with a different implementation?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extend your implementation for &lt;a href="https://en.wikipedia.org/wiki/Rock_paper_scissors#Additional_weapons"&gt;additional weapons&lt;/a&gt; (e.g. Rock Paper Scissors Lizard Spock). How easy or hard is this?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Advanced&lt;/strong&gt; - read about the &lt;em&gt;"open-closed principle"&lt;/em&gt;, &lt;a href="https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle"&gt;OCP&lt;/a&gt;. Can you refactor your code such that adding more weapons doesn't mean a change to the &lt;code&gt;rps&lt;/code&gt; function?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test-drive out some validation - what should your code do if either or both of the inputs aren't recognised shapes? If you decide to throw an error, note that per &lt;a href="https://jestjs.io/docs/en/expect#tothrowerror"&gt;the Jest docs&lt;/a&gt; you need to pass a function to defer execution: &lt;/p&gt;
&lt;p&gt;&lt;code&gt;expect(() =&amp;gt; rps("bananas", 123)).toThrowError();&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;otherwise the error's thrown too early and Jest can't handle it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Refactor the tests to group the test cases into three parameterised tests: one for &lt;code&gt;"left"&lt;/code&gt;; one for &lt;code&gt;"right"&lt;/code&gt;; and one for &lt;code&gt;"draw"&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Advanced&lt;/strong&gt; - use Jest's &lt;a href="https://jestjs.io/docs/en/api#testeachtablename-fn-timeout"&gt;&lt;code&gt;each&lt;/code&gt;&lt;/a&gt; method, either with an array or a template literal.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run through the exercise from the beginning, but using &lt;code&gt;npm t -- --watch&lt;/code&gt; instead of just &lt;code&gt;npm t&lt;/code&gt; (note the extra &lt;code&gt;--&lt;/code&gt;, otherwise the &lt;code&gt;--watch&lt;/code&gt; argument gets passed to NPM rather than Jest).&lt;/p&gt;
&lt;p&gt;This will set up Jest to watch your files and re-run the tests if anything changes. Arrange your screen so you can see these outputs while you're writing your code, and leave it running the whole time (if this means you can't make commits, don't worry about it). What impact does that immediate and continuous feedback have on your experience of working on the code?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run through the exercise with a different test framework, e.g.:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://mochajs.org/"&gt;Mocha&lt;/a&gt; with &lt;a href="https://www.chaijs.com/"&gt;Chai&lt;/a&gt; assertions (note it's &lt;code&gt;expect(foo).to.equal(bar)&lt;/code&gt; rather than &lt;code&gt;expect(foo).toBe(bar)&lt;/code&gt;); or&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Node's built-in &lt;a href="https://nodejs.org/api/test.html"&gt;test runner&lt;/a&gt; and &lt;a href="https://nodejs.org/api/assert.html"&gt;assertions&lt;/a&gt; (note it's &lt;code&gt;assert.equal(foo, bar)&lt;/code&gt; rather than &lt;code&gt;expect(foo).toBe(bar)&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Once you're ready to move on&lt;/strong&gt;, check out &lt;a href="https://blog.jonrshar.pe/2020/Nov/22/js-tdd-e2e.html"&gt;the next article&lt;/a&gt; in this series where we'll use &lt;em&gt;end-to-end&lt;/em&gt; and &lt;em&gt;integration&lt;/em&gt; testing to build out a React UI for this RPS logic.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Installation explained [Bonus]&lt;/h2&gt;
&lt;p&gt;As promised above, here's an explanation of everything you were told during the &lt;code&gt;npm install&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;npm&lt;span class="w"&gt; &lt;/span&gt;WARN&lt;span class="w"&gt; &lt;/span&gt;deprecated&lt;span class="w"&gt; &lt;/span&gt;request@2.88.2:&lt;span class="w"&gt; &lt;/span&gt;request&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;been&lt;span class="w"&gt; &lt;/span&gt;deprecated,&lt;span class="w"&gt; &lt;/span&gt;see&lt;span class="w"&gt; &lt;/span&gt;https://github.com/request/request/issues/3142
npm&lt;span class="w"&gt; &lt;/span&gt;WARN&lt;span class="w"&gt; &lt;/span&gt;deprecated&lt;span class="w"&gt; &lt;/span&gt;request-promise-native@1.0.9:&lt;span class="w"&gt; &lt;/span&gt;request-promise-native&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;been&lt;span class="w"&gt; &lt;/span&gt;deprecated&lt;span class="w"&gt; &lt;/span&gt;because&lt;span class="w"&gt; &lt;/span&gt;it&lt;span class="w"&gt; &lt;/span&gt;extends&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;now&lt;span class="w"&gt; &lt;/span&gt;deprecated&lt;span class="w"&gt; &lt;/span&gt;request&lt;span class="w"&gt; &lt;/span&gt;package,&lt;span class="w"&gt; &lt;/span&gt;see&lt;span class="w"&gt; &lt;/span&gt;https://github.com/request/request/issues/3142
npm&lt;span class="w"&gt; &lt;/span&gt;WARN&lt;span class="w"&gt; &lt;/span&gt;deprecated&lt;span class="w"&gt; &lt;/span&gt;har-validator@5.1.5:&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;library&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;no&lt;span class="w"&gt; &lt;/span&gt;longer&lt;span class="w"&gt; &lt;/span&gt;supported
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Over time, libraries published on NPM will become out-of-date. For various reasons, their maintainers may decide to stop supporting them, or to stop supporting older major versions. This isn't &lt;em&gt;necessarily&lt;/em&gt; a problem, but means that future vulnerabilities won't be addressed; try not to depend on deprecated packages.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;npm&lt;span class="w"&gt; &lt;/span&gt;notice&lt;span class="w"&gt; &lt;/span&gt;created&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;lockfile&lt;span class="w"&gt; &lt;/span&gt;as&lt;span class="w"&gt; &lt;/span&gt;package-lock.json.&lt;span class="w"&gt; &lt;/span&gt;You&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;file.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you look in the package file after the install, the only information about the dependencies is &lt;code&gt;"jest": "^26.4.2"&lt;/code&gt;. This means &lt;em&gt;"requires Jest of at least &lt;code&gt;v26.4.2&lt;/code&gt; and up to (but not including) &lt;code&gt;v27.0.0&lt;/code&gt;"&lt;/em&gt; (you can learn more about this notation with &lt;a href="https://semver.npmjs.com/"&gt;the semver calculator&lt;/a&gt;). Given the number of dependencies installed (see below), that's not a lot of information - if you installed this package on another machine, it might get a quite different set of dependencies. So that you can reproduce a specific install more easily, NPM stores the &lt;em&gt;exact&lt;/em&gt; versions of &lt;em&gt;all&lt;/em&gt; of the dependencies in &lt;code&gt;package-lock.json&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;npm&lt;span class="w"&gt; &lt;/span&gt;WARN&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@1.0.0&lt;span class="w"&gt; &lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;description
npm&lt;span class="w"&gt; &lt;/span&gt;WARN&lt;span class="w"&gt; &lt;/span&gt;rps-tdd@1.0.0&lt;span class="w"&gt; &lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;repository&lt;span class="w"&gt; &lt;/span&gt;field.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The shortcut we used to create the &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;npm init -y&lt;/code&gt;, created all of the &lt;em&gt;required&lt;/em&gt; fields but not all of the &lt;em&gt;recommended&lt;/em&gt; fields. If you want to avoid this warning in the future, add a description and repository into your &lt;code&gt;package.json&lt;/code&gt; per &lt;a href="https://docs.npmjs.com/files/package.json"&gt;the NPM docs&lt;/a&gt;. When creating new packages you can use &lt;code&gt;npm init&lt;/code&gt; instead to go through an interactive process to enter more of the fields.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;+&lt;span class="w"&gt; &lt;/span&gt;jest@26.4.2
added&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;506&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;347&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;contributors&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;audited&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;506&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;.837s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's the bit we actually care about; Jest (and all of its own dependencies) &lt;strong&gt;has been installed&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="m"&gt;21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;looking&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;funding
&lt;span class="w"&gt;  &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;fund&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;details
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Per &lt;a href="https://github.com/npm/rfcs/blob/2d2f00457ab19b3003eb6ac5ab3d250259fd5a81/accepted/0017-add-funding-support.md"&gt;RFC 0017&lt;/a&gt;, NPM allows package maintainers to solicit funding by adding information to their package files. As the message says, you can run &lt;a href="https://docs.npmjs.com/cli-commands/fund.html"&gt;&lt;code&gt;npm fund&lt;/code&gt;&lt;/a&gt; to see which packages are asking for financial support, and how to provide it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vulnerabilities
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;NPM checks whether there are any known vulnerabilities in the packages in your &lt;code&gt;node_modules/&lt;/code&gt;. This can tell you when you need to update your dependencies, especially anything you use in production (as opposed to development dependencies like Jest). You can run &lt;a href="https://docs.npmjs.com/cli-commands/audit.html"&gt;&lt;code&gt;npm audit&lt;/code&gt;&lt;/a&gt; at any time to get the latest updates and see more information.&lt;/p&gt;</content><category term="development"></category><category term="javascript"></category><category term="tdd"></category><category term="xp"></category></entry><entry><title>Automation for the people</title><link href="https://blog.jonrshar.pe/2019/Feb/10/automation-for-the-people.html" rel="alternate"></link><published>2019-02-10T15:30:00+00:00</published><updated>2022-02-08T15:28:00+00:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2019-02-10:/2019/Feb/10/automation-for-the-people.html</id><summary type="html">&lt;p&gt;Making developers' lives easier and more productive with package file automation&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you're working in the JavaScript ecosystem, your project is likely based
around a &lt;code&gt;package.json&lt;/code&gt; file. This is used by &lt;a href="https://docs.npmjs.com/cli-documentation/"&gt;NPM&lt;/a&gt; for defining the
attributes of your package, including its &lt;code&gt;"name"&lt;/code&gt; and &lt;code&gt;"version"&lt;/code&gt;. You may
have noticed that one of the other options in the package file is &lt;code&gt;"scripts"&lt;/code&gt;.
By default, this contains a single task:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;scripts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is an inauspicious start to what I think is a pretty powerful entry point
to high-level developer automation.&lt;/p&gt;
&lt;h2&gt;Why automate?&lt;/h2&gt;
&lt;p&gt;It's hopefully not controversial to state that developer time is &lt;em&gt;expensive&lt;/em&gt;
and you only want to spend that expensive, finite resource on things that are
valuable to the business. Automation can minimise the time spent on repeating
simple tasks that happen many times a day, like linting or testing the code.&lt;/p&gt;
&lt;p&gt;Package scripts provide a way to do this within the tooling your developers are
likely already using on a daily basis. They don't need to learn a new tool and
all of the existing NPM packages are available to help them out. It's also
easy to expand when you need to, because they can start writing more complex
processes as &lt;code&gt;.js&lt;/code&gt; Node scripts or &lt;code&gt;.sh&lt;/code&gt; shell scripts and still invoke them
via &lt;code&gt;npm run&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Using the package scripts as an entry point also allows you to keep a
consistent "developer API" across multiple projects. For example, when I
switched &lt;a href="https://www.npmjs.com/package/fauxauth"&gt;&lt;code&gt;fauxauth&lt;/code&gt;&lt;/a&gt; over to TypeScript, using package scripts for
all common tasks (running e.g. &lt;code&gt;npm run lint&lt;/code&gt; rather than calling &lt;code&gt;eslint&lt;/code&gt;
directly) meant that I could change the meaning of the scripts in &lt;a href="https://github.com/textbook/fauxauth/commit/135376876aca5c16f9fbe2d89a3389f3ddf9f2d8#diff-b9cfc7f2cdf78a7f4b91a753d10865a2"&gt;the package
file&lt;/a&gt; without having to change the commands I was executing
(or update the CI configuration). This could also be beneficial if your
developers work across multiple technologies; &lt;code&gt;npm run lint&lt;/code&gt; could call
&lt;code&gt;eslint&lt;/code&gt; for React in some projects and the Angular CLI's &lt;code&gt;ng lint&lt;/code&gt; in others,
but the developer experience would be consistent.&lt;/p&gt;
&lt;h3&gt;Sidebar: linting&lt;/h3&gt;
&lt;p&gt;One of the least valuable things for your developers to spend time on is what
the code looks like, particularly in the JavaScript ecosystem where you
rarely ship the code you're writing without some kind of transpilation or
bundling. You also don't want them to spend time trading commits back and
forth switching &lt;code&gt;'&lt;/code&gt; for &lt;code&gt;"&lt;/code&gt; and vice versa; the smaller the diffs, the easier
it is to figure out what's meaningfully changed when reviewing a commit, the
better (for the same reason I think trailing commas are a good thing!)&lt;/p&gt;
&lt;p&gt;In general, the less they have to think about, the better; although the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Automatic_semicolon_insertion"&gt;ASI&lt;/a&gt;
rules are fairly straightforward, for example, using semicolons everywhere means
they &lt;em&gt;never have to consider it&lt;/em&gt;. Modern tools can generally do this for them;
individuals can write the code how they want to, then auto-fix on type, save or
lint to the agreed shared rules. The team can now focus more of its time on
delivering the things that matter to your users.&lt;/p&gt;
&lt;p&gt;I also don't think you should have any warnings when you run linters; things
you actually care about get lost in the noise. Every rule should either be an
error or ignored completely, so it's unambiguous what's important, and any
error should fail the build. If you're using ESLint, you can get a list of
active warnings using the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;:::&lt;span class="w"&gt; &lt;/span&gt;bash
$&lt;span class="w"&gt; &lt;/span&gt;npx&lt;span class="w"&gt; &lt;/span&gt;eslint&lt;span class="w"&gt; &lt;/span&gt;--print-config&lt;span class="w"&gt; &lt;/span&gt;path/to/file.js&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;warn&lt;span class="w"&gt; &lt;/span&gt;-B&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Hints and tips&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;"task:step"&lt;/code&gt;: this is a very common convention for writing scripts, using
  a colon in the script name to indicate some kind of sub-step or configuration
  option. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;scripts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;test:cover&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;npm run test -- --coverage&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;or&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;scripts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;start&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;npm run start:compile &amp;amp;&amp;amp; npm run start:watch&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;start:compile&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;start:watch&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pre&lt;/code&gt; and &lt;code&gt;post&lt;/code&gt;: all package scripts get a "free" pre- and post- script hook.
  All you need to do is include another entry with the same name prefixed with
  &lt;code&gt;pre&lt;/code&gt; or &lt;code&gt;post&lt;/code&gt; and this script will get run at the appropriate point in the
  lifecycle, assuming everything so far has exited zero.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;scripts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;posttest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./collect-coverage.sh&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-- &amp;lt;args&amp;gt;&lt;/code&gt;: any arguments to &lt;code&gt;npm run thing&lt;/code&gt; are passed to &lt;code&gt;npm run&lt;/code&gt;, &lt;em&gt;not&lt;/em&gt;
  &lt;code&gt;thing&lt;/code&gt;. To pass arguments to &lt;code&gt;thing&lt;/code&gt; you need to include &lt;code&gt;--&lt;/code&gt;, to indicate
  the end of the arguments to &lt;code&gt;npm run&lt;/code&gt;. For example, to pass the argument
  &lt;code&gt;--port=3000&lt;/code&gt; to the &lt;code&gt;serve&lt;/code&gt; script, you'd do:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;serve&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Helpful libraries&lt;/h2&gt;
&lt;p&gt;Here are a few great "glue" libraries I've used frequently in other projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.npmjs.com/package/concurrently"&gt;&lt;code&gt;concurrently&lt;/code&gt;&lt;/a&gt;: run multiple processes at once. Very helpful
  for a local &lt;a href="https://github.com/textbook/cyf-app-starter/blob/7ed846f0cfea766b7136368ef78f9f1d1650ead4/package.json#L16"&gt;dev setup&lt;/a&gt; with watching processes on both the server and client
  builds, for example.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.npmjs.com/package/cross-env"&gt;&lt;code&gt;cross-env&lt;/code&gt;&lt;/a&gt;: set environment variables in your package scripts,
  cross-platform. If you're externalising configuration to env vars and want
  your project to run correctly on both Windows and *nix, this is a must.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cross-env&lt;/code&gt; was also the target of the first package discovered in the
&lt;a href="https://blog.npmjs.org/post/163723642530/crossenv-malware-on-the-npm-registry"&gt;&lt;code&gt;hacktask&lt;/code&gt;&lt;/a&gt; account, a set of malicious typo-squatting packages
that sent the user's environment variables to a remote server. Be careful
to double-check that you're installing what you think you are.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.npmjs.com/package/husky"&gt;&lt;code&gt;husky&lt;/code&gt;&lt;/a&gt;: turn package scripts into &lt;a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks"&gt;git hooks&lt;/a&gt;. Never again will you
  accidentally break the build by pushing code that doesn't pass the tests!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.npmjs.com/package/rimraf"&gt;&lt;code&gt;rimraf&lt;/code&gt;&lt;/a&gt;: &lt;code&gt;rm -rf&lt;/code&gt; for Node. Handy for clearing out output
  directories to ensure a clean state before running a build.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.npmjs.com/package/wait-on"&gt;&lt;code&gt;wait-on&lt;/code&gt;&lt;/a&gt;: wait for things to be ready. I've used this to run E2E
  tests against a local dev server, making sure the server is actually spun up
  and providing responses before kicking off the test suite.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Downsides&lt;/h2&gt;
&lt;p&gt;There are a couple of downsides I've noticed to doing automation predominantly
through the package file.&lt;/p&gt;
&lt;p&gt;One is simply &lt;em&gt;length&lt;/em&gt;; as you add more complexity to your automation, either
you have very long lines, or lots of sub-steps, or sometimes both. For
example, &lt;a href="https://github.com/HelpRefugees/project-flamingo/blob/93011e309ac3026b995c95e9dc1241fb05b5c553/package.json#L6-L51"&gt;this project&lt;/a&gt; that I worked on has 43 scripts,
steps and pre-/post- hooks for performing various tasks. This led to having a
separate &lt;a href="https://github.com/HelpRefugees/project-flamingo/wiki/Package-Scripts"&gt;wiki page&lt;/a&gt; listing what they are and what they do, as putting
comments in a JSON file is not straightforward (see &lt;a href="https://stackoverflow.com/q/14221579/3001761"&gt;&lt;em&gt;"How do I add comments to
package.json for npm install?"&lt;/em&gt;&lt;/a&gt; for one option). Now there
is a risk that, as the scripts are updated and added to, the wiki page does not
stay up-to-date with what they currently do. You could move the steps out to
shell or Node scripts, or use an additional tool like &lt;a href="https://www.npmjs.com/package/nps"&gt;&lt;code&gt;nps&lt;/code&gt;&lt;/a&gt;, but that's
yet another thing to think about.&lt;/p&gt;
&lt;p&gt;Another is the difficulty of handling arguments when &lt;em&gt;combining&lt;/em&gt; scripts. As
mentioned above you can split a complex process into multiple steps and call
each of them in turn:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;scripts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;process:first&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;process:second&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;process&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;npm run process:first &amp;amp;&amp;amp; npm run process:second&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But what if you want to pass an argument down to one of the sub-steps? &lt;code&gt;npm run
process -- --something&lt;/code&gt; won't do it, so you often end up writing several
scripts to call the same processes with different arguments.&lt;/p&gt;
&lt;h2&gt;Bonus round: Yarn&lt;/h2&gt;
&lt;p&gt;The other major packaging option in the Node ecosystem is &lt;a href="https://yarnpkg.com/"&gt;Yarn&lt;/a&gt;. This uses
the same &lt;code&gt;package.json&lt;/code&gt; file as NPM, so switching over is relatively
straightforward. I won't go into the pros and cons here, but one thing to
note is that running scripts and passing arguments is a little simpler in Yarn
than in NPM; to run a script and pass arguments, instead of:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;thing&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;--arg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;you can just do:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;yarn&lt;span class="w"&gt; &lt;/span&gt;thing&lt;span class="w"&gt; &lt;/span&gt;--arg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;p&gt;Here are some useful articles and references:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.npmjs.com/misc/config#shorthands-and-other-cli-niceties"&gt;NPM CLI flags&lt;/a&gt; from the official documentation&lt;/li&gt;
&lt;li&gt;&lt;a href="https://corgibytes.com/blog/2017/04/18/npm-tips/"&gt;NPM scripts: tips everyone should know&lt;/a&gt; by &lt;a href="https://corgibytes.com/"&gt;Corgibytes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/"&gt;How to use NPM as a build tool&lt;/a&gt; by Keith Cirkel&lt;/li&gt;
&lt;li&gt;&lt;a href="https://michael-kuehnel.de/tooling/2018/03/22/helpers-and-tips-for-npm-run-scripts.html"&gt;Helpers and tips for NPM run scripts&lt;/a&gt; by Michael Kuehnel&lt;/li&gt;
&lt;/ul&gt;</content><category term="development"></category><category term="code"></category><category term="javascript"></category><category term="npm"></category></entry><entry><title>Publishing npm packages with service accounts</title><link href="https://blog.jonrshar.pe/2019/Jan/14/npm-service-accounts.html" rel="alternate"></link><published>2019-01-14T19:40:00+00:00</published><updated>2020-11-01T12:30:00+00:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2019-01-14:/2019/Jan/14/npm-service-accounts.html</id><summary type="html">&lt;p&gt;2FA adds security to your npm account but complicates the process of publishing from CI; here's one way around that&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: In &lt;a href="https://github.blog/changelog/2020-10-02-npm-automation-tokens/"&gt;October 2020&lt;/a&gt; NPM added the option to generate Automation
tokens, which bypass 2FA even for accounts that have it enabled for
publication. Using service accounts still improves security by reducing the
number of packages a successful attacker gets access to, so the below
information may still be useful to you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Since October 2017, npm has supported &lt;a href="https://blog.npmjs.org/post/166039777883/protect-your-npm-account-with-two-factor"&gt;2-factor authentication&lt;/a&gt; (2FA). This
is good for security, and I've enabled it for both authorisation and publication
on my main account, but it's difficult to use if you want to publish packages
automatically from CI (e.g. using &lt;a href="https://docs.travis-ci.com/user/deployment/npm/"&gt;Travis CI&lt;/a&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ERR&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;publish&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;
&lt;span class="nv"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ERR&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;E401&lt;/span&gt;
&lt;span class="nv"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ERR&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;one&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;authenticator&lt;/span&gt;.
&lt;span class="nv"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ERR&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;provide&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;one&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;passing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nv"&gt;otp&lt;/span&gt;&lt;span class="o"&gt;=&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ran&lt;/span&gt;.
&lt;span class="nv"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ERR&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;already&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;provided&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;one&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;likely&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;either&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;typoed&lt;/span&gt;
&lt;span class="nv"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ERR&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;it&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;timed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;out&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Please&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;again&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I have found &lt;a href="https://medium.com/@sgyio/how-to-deploy-npm-package-with-2fa-enabled-on-write-49843bf493a8"&gt;one proof of concept&lt;/a&gt; method using Hashicorp's &lt;a href="https://www.vaultproject.io/"&gt;Vault&lt;/a&gt; to
provide a TOTP code as needed, but this requires a lot of external setup if
you're not already using Vault as part of your workflow.&lt;/p&gt;
&lt;p&gt;Instead, I've set up a second, "service" account to use when publishing
from CI. Here's how:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First, you need to create a new npm user to act as the service user for
    automated deploys. Unfortunately, npm doesn't currently give you a way to
    add a description to a user, but I've set the service user up with the same
    GitHub and Twitter handles as my main account, so it's hopefully clear who
    it belongs to. I've also given it a spiffy robot avatar via Gravatar. Enable
    2FA, but make sure it's only for &lt;em&gt;Authorization&lt;/em&gt;, not &lt;em&gt;Authorization and
    Publishing&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, create a token for use in the CI environment. Switch to the &lt;em&gt;Tokens&lt;/em&gt;
    tab (or navigate directly to &lt;code&gt;https://www.npmjs.com/settings/{user}/tokens&lt;/code&gt;)
    and click &lt;em&gt;Create New Token&lt;/em&gt;. This will need to be set to the &lt;em&gt;Read and
    Publish&lt;/em&gt; level.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, log in with your main account, visit the package you want to give
    the service account access to and switch to the &lt;em&gt;Admin&lt;/em&gt; tab (or navigate
    directly to &lt;code&gt;https://www.npmjs.com/package/{package}/access&lt;/code&gt;). In the
    &lt;strong&gt;Invite maintainer&lt;/strong&gt; section, type the name of your service account and
    click &lt;em&gt;Invite&lt;/em&gt;. You should then see something like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="fauxauth maintainers list" src="https://blog.jonrshar.pe/images/fauxauth-maintainers.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can use the service account token in your CI environment for automated
deploys, without needing to reduce the security of your main account. I'd
recommend creating one service account per package so a breach doesn't impact
multiple different projects (and, as always, using a password manager to create
long, secure passwords that aren't reused).&lt;/p&gt;
&lt;p&gt;One major downside is that you can't enable the &lt;em&gt;"Require Two Factor
Authentication to publish or modify settings"&lt;/em&gt; option for your package while
you're using non-2FA service accounts. If you have other maintainers, make sure
they &lt;em&gt;are&lt;/em&gt; using full 2FA on their main accounts.&lt;/p&gt;</content><category term="development"></category><category term="code"></category><category term="javascript"></category><category term="ci"></category><category term="npm"></category></entry><entry><title>({=}) Coed:Ethics 2018</title><link href="https://blog.jonrshar.pe/2018/Jul/17/coed-ethics-2018.html" rel="alternate"></link><published>2018-07-17T12:30:00+01:00</published><updated>2018-07-17T12:30:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2018-07-17:/2018/Jul/17/coed-ethics-2018.html</id><summary type="html">&lt;p&gt;My experience of the inaugural Coed:Ethics conference on ethical issues in the tech industry&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last Friday I travelled (all the way!) to the &lt;a href="https://developer.microsoft.com/en-us/reactor/"&gt;Microsoft Reactor&lt;/a&gt; community
hub in Shoreditch to attend the first ever &lt;a href="https://www.coedethics.org/"&gt;Coed:Ethics conference&lt;/a&gt;, the
first event of its kind dedicated to exploring the ethical issues that face
software developers and other technologists. The conference consisted of eight
talks, followed by a panel and open discussion.&lt;/p&gt;
&lt;h2&gt;Talks&lt;/h2&gt;
&lt;p&gt;Eight half-hour talks made up the bulk of the day, from a range of speakers
across numerous topics around ethics in technology. The videos should be
available in the near future, but I've written my summary of each talk below.&lt;/p&gt;
&lt;h3&gt;When Data Kills&lt;/h3&gt;
&lt;p&gt;The first ever Coed:Ethics talk was given by &lt;a href="https://www.coricrider.com/"&gt;Cori Crider&lt;/a&gt;, a human rights
lawyer. Her talk focused on tech companies' involvement with the military,
covering topics like machine learning for drone strike targeting. She also
talked about the efforts to draw up principles governing the development and
use of AI, for example prohibiting its usage in weapons systems.&lt;/p&gt;
&lt;p&gt;One event she talked about was Google's involvement in Project Maven, a
research program aimed at improving object recognition in miltary drones.
Following an open letter signed by thousands of employees and at least a
dozen resignations, Google announced that it would not be renewing the contract.
This illustrates the power that employees in technology have to affect the
decisions their companies are making, especially if they are willing to stand
up in public for what they believe in. The people who are building these
technologies need to have a say in how it is being used.&lt;/p&gt;
&lt;p&gt;For me, a key question from her talk was: &lt;em&gt;"what is the line between lethal
and non-lethal help?"&lt;/em&gt; My employer &lt;a href="https://www.fastcompany.com/40588729/the-air-force-learned-to-code-and-saved-the-pentagon-millions"&gt;recently worked&lt;/a&gt; with the US Air Force
on a project to improve the efficiency of scheduling aircraft refuelling
flights. Is that far enough removed for comfort?&lt;/p&gt;
&lt;h3&gt;What is a Data Citizen?&lt;/h3&gt;
&lt;p&gt;Next up was &lt;a href="https://inamerryhour.com/"&gt;Caitlin McDonald&lt;/a&gt;, talking about what data citizenship looks
like by drawing parallels between our civic lives in relation to law
and the government and our relationship with data science.&lt;/p&gt;
&lt;p&gt;She cited &lt;a href="https://weaponsofmathdestructionbook.com/"&gt;&lt;em&gt;"Weapons of Math Destruction"&lt;/em&gt;&lt;/a&gt; by Cathy O’Neil, which makes
argues that there are particular &lt;em&gt;kinds&lt;/em&gt; of application that cause problems;
those that lack feedback cycles for improvement, for example. Caitlin
suggested that we as "data citizens" deserve to know what the rules we are
being evaluated by &lt;em&gt;are&lt;/em&gt; and have a structure to &lt;em&gt;challenge&lt;/em&gt; them (just as we
can read the law and appeal a legal decision in civic society).&lt;/p&gt;
&lt;p&gt;She introduced several existing tools to support ethical decision making,
including the Open Data Institute's &lt;a href="https://theodi.org/article/data-ethics-canvas/"&gt;Data Ethics Canvas&lt;/a&gt;, but made the
point that you also need a &lt;em&gt;culture of ethics&lt;/em&gt; in a company. It's not enough
to apply the toolkit, you also need people willing to listen to the results
and act on them.&lt;/p&gt;
&lt;h3&gt;Data Science in Action&lt;/h3&gt;
&lt;p&gt;The third talk was by Emma Prest and Clare Kitching from &lt;a href="http://www.datakind.org/"&gt;DataKind&lt;/a&gt;, a
group that &lt;em&gt;"[brings] together top data scientists with leading social change
organizations to collaborate on cutting-edge analytics and advanced algorithms
to maximize social impact"&lt;/em&gt;. In particular, they talked about the need to
embed ethics throughout the data science process, illustrating their points
with an example of working with a food bank to identify individuals in need
of further counselling.&lt;/p&gt;
&lt;p&gt;They suggested building ethical concerns into the project right from the
scoping and kick-off stages, beginning by considering what the high-level
risks around worse-case outcomes and data could be. Then, as the project
continues, building bias assessments into the data and algorithm checking.
They highlighted the importance of involving a diverse range of people in the
process; not just the clients and the data scientists, but also the
developers, the users and other stakeholders.&lt;/p&gt;
&lt;p&gt;One particularly interesting point they made was the need to consider whether
the model being developed is appropriate for the &lt;em&gt;"data maturity"&lt;/em&gt; of the
organisation in question. In case study there was a board member with
experience in maths and data science, but what if the model continues to be
applied after they have left? How do you ensure ongoing governance of the
model and embed understandability and transparency into the process, so that
it doesn't end up getting misused or misapplied in the future?&lt;/p&gt;
&lt;p&gt;Another was the impact of open sourcing tools and models; their open question
was whether it is better to make these things open source so that the broader
community can gain maximum benefit, or whether it's actually preferable
overall to keep a higher level of control and governance by keeping them
proprietary? Thinking about the context and the value is important here, are
we making things &lt;em&gt;better enough&lt;/em&gt;?&lt;/p&gt;
&lt;h3&gt;Psychology of Ethics 101&lt;/h3&gt;
&lt;p&gt;The final talk of the morning was by &lt;a href="https://twitter.com/andrea_kock"&gt;Andrea Dobson&lt;/a&gt;, a psychologist
interested in what makes good people make bad decisions. She talked about
Milgram's work on obedience, highlighting the result that around 75% of people
will conform to something that they know is wrong, and that conformance is
actually &lt;em&gt;more&lt;/em&gt; likely in more ambiguous circumstances. Given that we are
generally taught to defer to figures of authority, how can individual
contributors effectively raise the concerns to management?&lt;/p&gt;
&lt;p&gt;This applies to technology, too; Andrea related the results of the &lt;a href="https://insights.stackoverflow.com/survey/2018/"&gt;Stack
Overflow survey&lt;/a&gt;, where nearly 60% of respondents said that upper
management was ultimately most responsible for code that accomplishes something
unethical.&lt;/p&gt;
&lt;p&gt;She advised listening to your emotions: do you feel guilty about what you're
doing? Why are you doing it? What matters to you personally?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Companies become ethical one person and one decision at a time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One thing that can help this is providing an environment of psychological
safety, where individuals feel comfortable speaking up and voicing their
concerns. If you don't feel you have that, she suggested speaking to other
people (HR, regulatory bodies, friends, family, ...) who can help you figure
it out.&lt;/p&gt;
&lt;h3&gt;Thinking Ethically at Scale&lt;/h3&gt;
&lt;p&gt;After lunch &lt;a href="https://twitter.com/YanqingCheng"&gt;Yanqing Cheng&lt;/a&gt; talked about how and why we might care about
ethics, and how we can manage to both make the world a better place &lt;em&gt;and&lt;/em&gt;
feel like we're doing so.&lt;/p&gt;
&lt;p&gt;She highlighted the issue of human intuition and its failure to scale - for
example, people would donate roughly $80 dollars to help birds in oil slicks
whether they were told it affected 2,000, 20,000 or 200,000 birds. Part of the
problem is simply being bad at imagining large numbers; when she reframed
worldwide road traffic deaths as being more than every British Airways flight
from London to New York for a year crashing into the sea, that really brought
home the magnitude in a way the number alone didn't. The key lesson in that the
most effective actions are not always the most intuitive.&lt;/p&gt;
&lt;p&gt;She talked about the Effective Altruism movement, trying to measure the
impact of different charities so that individuals can maximise the impact of
their personal donations. The &lt;strong&gt;Who&lt;/strong&gt; (can have an impact on your goal),
&lt;strong&gt;How&lt;/strong&gt; (can they help or obstruct), &lt;strong&gt;What&lt;/strong&gt; (can I do to affect their
behaviour) model, a useful brainstorming technique, can be used to try to
identify the actions that give the biggest payoff for the largest number of
people and focus on what you can achieve as an individual.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Bonus points for being the only conference talk I've ever seen that included
lessons from Harry Potter fan fiction.)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Ethical Design&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://www.harrytrimble.co.uk/"&gt;Harry Trimble&lt;/a&gt; was next, talking about the power that designers hold in
a world where software is everywhere, and their responsibility to give power
to those without it. He commented that ethical decisions are unavoidable,
even if the only option you may have is refusing to do the work or quitting
entirely. In particular, he talked about the issues around authentication and
consent in data management.&lt;/p&gt;
&lt;p&gt;Two topics stood out for me:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Informed, shared and delegated consent, allowing groups and communities to
    participate effectively in decision making or involve other parties that
    might have more appropriate information; and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Accountability and transparency in automated decision making, for example
    providing a "snapshot" that identifies the version of the system and its
    state so that you can review and appeal the decision.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One interesting tool he talked about was the &lt;a href="https://catalogue.projectsbyif.com/"&gt;Data Permissions Catalogue&lt;/a&gt;,
a collection of design patterns for sharing data. The idea of tools like this
is to bring a shared language to ethical issues, making them easier to
describe and discuss. This reminded me of one of the core ideas of
domain-driven design (DDD), the importance of having a shared, consistent
vocabulary (the &lt;em&gt;"ubiquitous language"&lt;/em&gt;) within teams and projects.&lt;/p&gt;
&lt;h3&gt;A Responsible Dev Process?&lt;/h3&gt;
&lt;p&gt;This was a two-part talk, starting with &lt;a href="https://twitter.com/adamsand0r"&gt;Adam Sándor&lt;/a&gt; talking about how
the idea of breaking down silos within companies and giving responsibility
and autonomy to teams can help people move from the idea that they're just a
small cog in a large machine to actually having ownership of what they are
building. These multi-disciplinary teams are more inclusive and having the
responsibility means you're more likely to make the right ethical decisions.&lt;/p&gt;
&lt;p&gt;Next &lt;a href="https://twitter.com/samcatbrown"&gt;Sam Brown&lt;/a&gt; from Doteveryone talked about their work in &lt;a href="https://medium.com/doteveryone/introducing-the-three-cs-of-responsible-technology-5e1d7fae558"&gt;Responsible
Technology&lt;/a&gt;, defined as technologies that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Do not knowingly create or deepen existing inequalities;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Recognise and respect everyone’s rights and dignity; and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Give people confidence and trust in their use.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;She talked about the power that technologists have as people with the skills
to turn ideas into reality and introduced the "3C model", framing ethical
conversations around Context, Consequence and Contribution. Doteveryone are
working on assessments and tools to support the discussion, providing a model
for responsible practice and making responsibility the new normal, a key
business driver for growth and innovation rather than a side concern.&lt;/p&gt;
&lt;p&gt;One question from the audience was whether adding more regulation and
associated cost to conform with security and other standards would entrench the
positions of larger companies and discourage smaller companies and startups.
One suggestion in response was being open about products' status, giving users
information about what has been done and what is planned. But as a company or
group and as individuals it's important to know your limits; if you're a
bridge-building startup, your bridges still have to &lt;em&gt;work&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;How to Build a Good Product&lt;/h3&gt;
&lt;p&gt;The final talk of the conference was from Steve Worswick, creator of the
&lt;a href="https://pandorabots.com/mitsuku/"&gt;Mitsuku&lt;/a&gt; chatbot (and possibly the only man to have both three Loebner
prizes and two tracks on Scottish Clubland 3). He talked about some of the
ethical decisions he made when developing the bot, which is a general-purpose
chatbot rather than a virtual assistant like Siri or Alexa.&lt;/p&gt;
&lt;p&gt;One specific decision was to train Mitsuku using supervised learning, rather
than allowing unsupervised learning like the infamous &lt;a href="https://www.technologyreview.com/s/601111/why-microsoft-accidentally-unleashed-a-neo-nazi-sexbot/"&gt;Tay chatbot&lt;/a&gt;. This
is extremely time-consuming, especially when trying to keep up-to-date with
current affairs so Mitsuku can respond appropriately, but allows him to keep
the bot family-friendly (despite the revenue temptation from more "romantic"
users). It can learn from a specific user, but &lt;em&gt;only for that user&lt;/em&gt;, then
Steve chooses which rules to add to the general set.&lt;/p&gt;
&lt;p&gt;Another set of interesting decisions was in dealing with the ~30% of users
who are abusive in some way (50% are "normal" users, the other 20% skeptics
and academics). Tame responses only seemed to encourage bullies, so the bot
currently uses a "5 strikes and you're out" system, combined with a flag on
the user that allows the bot to respond more strongly to abusive users
(although it never swears back at them).&lt;/p&gt;
&lt;h2&gt;Discussion&lt;/h2&gt;
&lt;p&gt;The day finished with a panel and open discussion. A lot of different points
were talked about, I've summarised a few below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Education vs. regulation&lt;/strong&gt; - where do we start? We need to educate
    politicians in the issues before we can talk sensibly about regulation. But
    laws are often behind the times, and in a fast-moving industry we have a
    responsibility to do something.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ethics as a product&lt;/strong&gt; - can companies use ethics as a competitive
    advantage? Are there any downsides to doing so? People want to engage
    with ethical companies, but misuse can lead to cynicism if those
    principles aren't seen to be upheld.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Not Invented Here&lt;/strong&gt; - what prior art is there, is this really something
    we need to figure out outselves? Psychologists and doctors, for example,
    already have mandatory ethics training and advice panels. We don't
    currently ask about ethics in interviews, so even where CS students have
    ethics modules they aren't seeing the benefit when it comes to entering
    the profession.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Is Apple ethical&lt;/strong&gt; - should I get a different phone? The most ethical
    smartphone available is keeping the one you already have. Issues exist
    around the supply chain, e.g. conflict minerals, but Apple are doing good
    work around privacy and federated machine learning. There is a bottom
    line below which we won't accept a company, but it's hard to have a
    binary yes or no for most, especially when their products give a lot of
    utility.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;A few key thoughts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Understanding power&lt;/strong&gt;: it's important for tech workers of all kinds to
    understand the power they hold. Software is, as we're often reminded,
    "eating the world", and as the industry grows skilled people will continue
    to be in high demand and have power as a result.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Collective action&lt;/strong&gt;: it would be easy to think that, because the
    working conditions and benefits in tech companies are already generally
    good, we have less need of unions and professional bodies, but it was clear
    from the discussion that there is a role for these groups in driving issues
    like ethics across the community. For example, the &lt;a href="https://techworkerscoalition.org/"&gt;Tech Workers
    Coalition&lt;/a&gt; is working toward an inclusive and equitable tech industry,
    covering all kinds of workers involved in the industry. In a timely
    fashion, as I write this, the &lt;a href="https://www.acm.org/"&gt;ACM&lt;/a&gt; is about to publish an updated
    ethical standard for the computing profession.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Diverse groups&lt;/strong&gt;: a few of the talks touched on the idea of involving
    more people, and more diverse groups, in discussions and decision making.
    This helps make sure we aren't missing simple problems that are just not
    obvious from our own point of view. Especially when building consumer
    products it's important to build groups that reflect broader populations
    and can bring more ideas and ways of solving problems to the table.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Finally, I'd like to thank the organisers for their work; the day went very
smoothly and it was great to see such a diverse group of speakers and
attendees talking about this important issue. Hopefully this is just the
start of something much bigger!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure&lt;/strong&gt;: Pivotal paid for my attendance of this conference as part of
my professional development budget, and were a Gold level sponsor of the
conference. However, I have written this article as an individual; it
reflects my position and opinions, not necessarily those of my employer.&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="work"></category><category term="ethics"></category><category term="conferences"></category></entry><entry><title>Introduction to Pair Programming</title><link href="https://blog.jonrshar.pe/2017/Oct/13/ada-college-pairing.html" rel="alternate"></link><published>2017-10-13T10:20:00+01:00</published><updated>2021-11-06T16:30:00+00:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2017-10-13:/2017/Oct/13/ada-college-pairing.html</id><summary type="html">&lt;p&gt;Why do we pair program at Pivotal, and could you be doing it too?&lt;/p&gt;</summary><content type="html">&lt;p&gt;As part of Pivotal London's grassroots diversity and inclusion efforts, a
colleague and I have recently been talking to the team at &lt;a href="https://ada.ac.uk"&gt;Ada College&lt;/a&gt;,
the UK's National College for Digital Skills, about their apprenticeship
program.&lt;/p&gt;
&lt;p&gt;They invited us to lead an introductory session on pair programming to around
40 of their higher level digital apprentices. This is the material from that
session, rewritten as a blog post.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pairing Table in San Francisco" src="https://blog.jonrshar.pe/images/Process-Pairing-01.jpg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;"Pairing Table in San Francisco"&lt;/em&gt;, &amp;copy; Pivotal&lt;/small&gt;&lt;/p&gt;
&lt;h2&gt;Why do you pair?&lt;/h2&gt;
&lt;p&gt;Pair programming is one of the core practices of Extreme Programming (XP), a
software development methodology based on the principles laid out in &lt;a href="http://agilemanifesto.org"&gt;the
agile manifesto&lt;/a&gt;. A major part of XP, and any agile methodology, is feedback:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;XP teams strive to generate as much feedback as they can handle as quickly
as possible.&lt;/p&gt;
&lt;p&gt;&lt;small&gt;from &lt;em&gt;Extreme Programming Explained: Embrace Change&lt;/em&gt; by Kent Beck
with Cynthia Andres&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With pair programming, you're getting immediate feedback on your code, as
you're writing it. This also aligns with the idea of Extreme Programming being
good practices taken to extremes - it's code review, turned all the way up.
The specific practice is described as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Write all production programs with two people sitting at one machine...
Pair programmers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep each other on task.&lt;/li&gt;
&lt;li&gt;Brainstorm refinements to the system.&lt;/li&gt;
&lt;li&gt;Clarify ideas.&lt;/li&gt;
&lt;li&gt;Take initiative when their partner is stuck, thus lowering frustration.&lt;/li&gt;
&lt;li&gt;Hold each other accountable to the team's practices.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;small&gt;from &lt;em&gt;Extreme Programming Explained: Embrace Change&lt;/em&gt; by Kent Beck
with Cynthia Andres&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In a similar vein the Pivotal Labs &lt;a href="https://pivotal.io/labs"&gt;website&lt;/a&gt; lists four benefits of
pairing, for all of the roles in a balanced product team, which I'll address in
turn in the following sections:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Boost efficiency through collaboration&lt;/li&gt;
&lt;li&gt;Knowledge share and skill transfer&lt;/li&gt;
&lt;li&gt;Prevent knowledge silo&lt;/li&gt;
&lt;li&gt;100% transparency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, perhaps the most succinct response to the question is the following
quote from Rob Mee, Pivotal Labs founder and current Pivotal CEO:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It's more efficient, and it produces better code.&lt;/p&gt;
&lt;p&gt;&lt;small&gt;from &lt;a href="https://www.wired.com/2013/11/pivotal-one/"&gt;This Company Believes You Should Never Hack Alone&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Specifically for new developers, pairing can be an effective way to ramp up
quickly, avoiding impostor syndrome and the stress of feeling abandoned to fend
for yourself in an unfamiliar codebase. &lt;a href="https://twitter.com/amesimmons"&gt;Amy Simmons&lt;/a&gt; recently gave at
talk at Pivotal's public lunch and learn series on &lt;a href="http://amysimmons.github.io/a-guide-to-the-care-and-feeding-of-new-devs/"&gt;the care and feeding of
new devs&lt;/a&gt;, discussing the results of a survey that found that 81% of
respondents thought that junior devs could be better supported; one of her
recommendations was to adopt code reviews and pair programming with senior
devs, at least on a weekly basis.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Boost efficiency through collaboration&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The immediate benefit to pairing is how infrequently you find yourself stuck.
If there's something you don't know how to do, or you aren't sure where to go
next, nine times out of ten your pair will be able to get you moving again.
It's like rubber duck debugging, except that now the rubber duck can provide
additional ideas!&lt;/p&gt;
&lt;p&gt;In addition pairing is great for maintaining focus; whereas it's easy to get
sidetracked when you're working alone (especially with a high-speed
connection to social networks, news websites and TV Tropes...), having a second
person looking at your screen keeps you honest about what you're supposed to
be working on. In my experience the "flow state" of pairing is different to
the one you get into when working alone; it's easier to get into and more
resilient to interruptions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Knowledge share and skill transfer&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As an engineer in Pivotal Labs, the majority of my job consists of pair
programming with our client's engineers, with the aim being to help them
develop the software they're working on and build an effective team that can
continue collaborate effectively once the engagement ends and they return to
their usual offices. I have found pair programming to be a very effective way
to teach the other practices we follow, like test-driven development (TDD)
and continuous integration and deployment (CI/CD).&lt;/p&gt;
&lt;p&gt;It's also a productive way to introduce someone to a new technology or
codebase. Pairing with someone with less experience helps to identify where
the code could be clearer or easier to follow, which is easy to overlook when
you're more comfortable with the language or domain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prevent knowledge silo&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Have you ever started trying to work on something only to be told &lt;em&gt;"Oh,
that's so-and-so's part, you should leave it to them"&lt;/em&gt;? Or, even worse, &lt;em&gt;"the
person who did that left, so we try not to touch it"&lt;/em&gt;? Pairing helps to avoid
this by ensuring that no single person is the only one with knowledge of any
specific part of the product.&lt;/p&gt;
&lt;p&gt;This is especially powerful when you're regularly rotating pairs. Generally
we rotate on a daily basis, using tools like &lt;a href="https://parrit.io/"&gt;Parrit&lt;/a&gt; to make sure that
every possible combination is occurring. If a story is still in flight from
the previous day we &lt;em&gt;"stick and twist"&lt;/em&gt;, with one person staying with the
story to pass along the context and the other moving on to something else.
This has the natural side effect that the more complex stories, the ones that
take multiple days, get more people's input.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;100% transparency&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;On your own you might be tempted to rush, to cut corners; &lt;em&gt;"I'll just skip
writing this test"&lt;/em&gt;, or &lt;em&gt;"this isn't great but we can refactor it later"&lt;/em&gt;.
All-too-frequently, later never comes! Your pair can be your conscience,
keeping the code quality high and suggesting tweaks or possible edge cases as
you go along. They can also point out when you're overthinking something &lt;a href="https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it"&gt;you
aren't gonna need&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Anatomy of a pairing station&lt;/h2&gt;
&lt;p&gt;There are a few crucial things you need to pair effectively:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Shared desk&lt;/strong&gt;: sit close enough that you can easily talk about what
    you're working on. If you have access to them, sit-stand desks allow more
    flexibility to change your position throughout the day. If the two people
    are different heights, monitor risers can be useful to keep them both
    comfortable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Separate peripherals&lt;/strong&gt;: while it's possible to pair by passing a single
    keyboard and mouse back and forth, it isn't ideal. It's much easier to
    work with a set each, although you have to be careful not to jump in and
    use them at the same time! Each developer should also have their own
    mirrored monitor.&lt;/p&gt;
&lt;p&gt;This is particularly important when the pairing station is a laptop -
having two people trying to look at the same relatively small screen, in
close proximity to where one of them is trying to type, is very awkward.
At the very least have an external monitor, mouse and keyboard, either so
one can use those while the other uses the laptop, or so you can swap
back and forth more easily.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pen and paper&lt;/strong&gt;: whether you're discussing potential architectures,
    explaining a workflow, or just want to take a quick TODO note, nothing is
    as low friction as simply writing it down. A Post-It note on the bottom
    edge of a monitor can help record valuable information without
    interrupting the flow of pairing, and a quick sketch in a notebook is
    often the easiest way to share a complex idea.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Standard config&lt;/strong&gt;: to keep both partners efficient, you need to agree
    on consistent tooling and settings up-front. The pair should adopt a
    specific IDE or editor, key bindings, shortcuts, etc. This will likely mean
    compromise on some preferred tools, but is all part of taking collective
    ownership of everyone's productivity, as well as of the code. Similarly,
    agree on styling conventions, and strongly consider making linting part of
    your continuous integration process to keep everyone honest.&lt;/p&gt;
&lt;p&gt;If you're working in a team across multiple machines, it can be useful to
create scripts to set up each machine identically without manual work.
For example, the standard configuration for a Pivotal workstation is
provided by a set of Bash scripts, available &lt;a href="https://github.com/pivotal/workstation-setup"&gt;on GitHub&lt;/a&gt;. Another
useful tool some of my colleagues have been working with recently is
&lt;a href="https://www.ansible.com/"&gt;Ansible&lt;/a&gt;, which can be used to automate a wide range of tasks
including workstation setup.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Pairing roles&lt;/h2&gt;
&lt;p&gt;Each person in the pair has a specific role. Note that this isn't a permanent
role; you can (and should!) switch frequently between the two.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Driver&lt;/strong&gt;: the driver is responsible for actually implementing the ideas
    being discussed as code. They write out all production and test code,
    refactor existing code as required and regularly run the tests to get the
    automated feedback on how it is going.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Navigator&lt;/strong&gt;: the navigator is responsible for aligning the driver's
    progress towards the overall goal. They provide suggestions on possible
    directions or course corrections. They can also point out any minor typo,
    but note that they should give the driver a few seconds to spot it
    themselves first!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both people should be continually vocalising their thought processes; how do
you think the task is going? What should you be trying to do next. It's also
important to remain patient and kind when working so closely with someone for
long periods of time.&lt;/p&gt;
&lt;h2&gt;Different methods of pairing&lt;/h2&gt;
&lt;p&gt;Within the basic principles outlined above, there are many specific variations.
Here are a couple you can try out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Switch on red&lt;/strong&gt;: named for the Red step of test-driven development's
    Red-Green-Refactor process. Each person writes the code to pass the
    current failing test, then the next failing test, so you switch roles
    when the test status is "red".&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Blitz&lt;/strong&gt;: this is a time-based exercise, where you set an overall time
    for a task (e.g. ten minutes) and allot each person half of it. Have a
    countdown running while each person is driving - once their time is up,
    they can't drive any more. This is a good way to even up the driving and
    navigating time, and can be done with a physical chess clock. You can
    increase the total time as you get more comfortable with it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Evil coder&lt;/strong&gt;: one person writes all of the tests, and the other then
    tries to make each test pass in an unexpected (or even actively
    counter-productive) way. This forces the test writer to drive out the actual
    behaviour they are looking for by constraining the code through
    additional tests. This can be carried out silently, with the pair
    communicating their intentations solely through the code they're writing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mobbing&lt;/strong&gt;: more than two programmers working on the same task is
    referred to as "mobbing". This can be done with a single big screen and
    shared peripherals (ideally wireless, to make it easier to pass them
    around), as it's much harder to make sure no two people are typing at the
    same time in a larger group. I've found this particularly useful early in a
    project, where there isn't much surface area for multiple pairs to cover,
    or when introducing multiple new people to an existing codebase.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Things to avoid&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Loud noises&lt;/strong&gt;: try not to talk too loudly, and get away from any loud
    noise sources before starting. There's a hum to a roomful of people
    pairing, but it shouldn't feel like you need to shout over what's going
    on around you. Equally you shouldn't be talking over your pair; pairing
    is about reaching a consensus on the approach to take.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Strong smells&lt;/strong&gt;: it should go without saying that personal hygiene is
    important when you're sitting right next to someone all day. But it's
    also worth bearing in mind that some people are sensitive to smells that
    you think are nice; consider unscented toiletries and avoid strong
    perfume or aftershave.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Taking over&lt;/strong&gt;: Don't start using the mouse and keyboard while your pair
    is trying to, it's very frustrating when the cursor suddenly runs away
    from you. Ask explicitly or make a clear move when you want to try
    something out. It can be helpful to push the peripherals away from you,
    to avoid nudging them accidentally and also give you a clear way to
    signal that you want to drive.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mobile phones&lt;/strong&gt;: it's just too easy to get distracted by your phone.
    Consider turning on a do-not-disturb mode, or at the very least ensure
    it's on silent. Try to take breaks mindfully - use short pauses in your
    pairing (e.g. running a test suite) to discuss what you've been working
    on, rather than immediately picking up your phone. It's useful to have a
    second computer for separate research, but keep it shut - open email is
    another distraction.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;When not to pair&lt;/h2&gt;
&lt;p&gt;Like many of the practices of XP, pair programming is a good default but
doesn't fit every situation. For example, you might find it more effective to
split up and solo when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Researching a problem you’re stuck on&lt;/strong&gt;: people read at different rates
    and absorb new ideas in different ways;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Trying out new technology&lt;/strong&gt;: libraries, frameworks, new paradigms; or&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Taking breaks&lt;/strong&gt;: constant pairing can be tiring, especially when you’re
    new to it; don’t be afraid to ask for some time alone if you need it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In general, you should agree specific end goals and time limits when you
split up, so you can both be working towards the same thing, and you should
bring &lt;em&gt;ideas&lt;/em&gt;, rather than specific code or implementations, back to work on
with your pair.&lt;/p&gt;
&lt;h2&gt;Additional resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;“Pair Programming Illuminated”&lt;/em&gt; by Laurie Williams and Robert Kessler&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;“Strengthening the Case for Pair Programming”&lt;/em&gt; by Williams, Kessler,
    Cunningham and Jeffries [&lt;a href="https://collaboration.csc.ncsu.edu/laurie/Papers/ieeeSoftware.PDF"&gt;PDF&lt;/a&gt;]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;“The Costs and Benefits of Pair Programming”&lt;/em&gt; by Cockburn and Williams
    [&lt;a href="https://collaboration.csc.ncsu.edu/laurie/Papers/XPSardinia.PDF"&gt;PDF&lt;/a&gt;]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;“Pair Programming Configurations”&lt;/em&gt; by Fred Mastropasqua [&lt;a href="https://www.clearlyagileinc.com/blog/2016/5/20/pair-programming-configurations"&gt;Blog&lt;/a&gt;]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;“Is pair programming worth the trade off in engineering resources?”&lt;/em&gt; by
    Kent Beck [&lt;a href="https://www.quora.com/Is-pair-programming-worth-the-trade-off-in-engineering-resources/answer/Kent-Beck?srid=uL5a"&gt;Quora&lt;/a&gt;]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;“Pair Programming: When and Why it Works”&lt;/em&gt; by Chong, Plummer, Leifer,
    Klemmer, Eris and Toye [&lt;a href="http://hci.stanford.edu/publications/2005/pairs/PairProgramming-WhenWhy.pdf"&gt;PDF&lt;/a&gt;]&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: this post was revised to separate the &lt;em&gt;roles&lt;/em&gt; of pairing (driver
and navigator) from the &lt;em&gt;methods&lt;/em&gt; (e.g. blitz), as the presentation was
similarly updated.&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="work"></category><category term="pivotal"></category><category term="pairing"></category><category term="xp"></category></entry><entry><title>Converting to the Angular HttpClient</title><link href="https://blog.jonrshar.pe/2017/Sep/11/http-client-conversion.html" rel="alternate"></link><published>2017-09-11T22:50:00+01:00</published><updated>2017-09-11T22:50:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2017-09-11:/2017/Sep/11/http-client-conversion.html</id><summary type="html">&lt;p&gt;A summary of the experience of converting a simple Angular project from Http to the new HttpClient&lt;/p&gt;</summary><content type="html">&lt;p&gt;As I mentioned in &lt;a href="https://blog.jonrshar.pe/2017/Jul/15/angular-http-client.html"&gt;a previous article&lt;/a&gt;, Angular 4.3 introduced a new API for
making HTTP calls, supplementing the &lt;code&gt;HttpModule&lt;/code&gt; in &lt;code&gt;@angular/http&lt;/code&gt; with the 
new &lt;code&gt;HttpClientModule&lt;/code&gt; in &lt;code&gt;@angular/common/http&lt;/code&gt;. I experimented with the new
interceptors and request progress API, but didn't actually try it out in a
working application.&lt;/p&gt;
&lt;p&gt;So below is the results of an experiment to switch an existing project, &lt;a href="https://github.com/textbook/salary-stats"&gt;Salary
Stats&lt;/a&gt;, over to the &lt;code&gt;HttpClient&lt;/code&gt; and test drive the new API for both the
client and the tests. Despite the clear warnings in the documentation I'm using
the Angular &lt;a href="https://www.npmjs.com/package/angular-in-memory-web-api"&gt;in-memory web API&lt;/a&gt; to allow the user to make CRUD operations
without setting up a backend; this needed an upgrade to v0.4.0 to support the
new client.&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;Firstly, there's configuration. Using &lt;code&gt;Http&lt;/code&gt;, the best way to test a service
was to replace the &lt;code&gt;ConnectionBackend&lt;/code&gt; with a special &lt;code&gt;MockBackend&lt;/code&gt; that
provides access to active connections. Using Angular's dependency injection
(DI) through the &lt;code&gt;TestBed&lt;/code&gt;, this was reasonably straightforward:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configureTestingModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ConnectionBackend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;useClass&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;MockBackend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;RequestOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;useClass&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;BaseRequestOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;PersonService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PersonService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ConnectionBackend&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Http&lt;/code&gt; class has two injected dependencies, so we can either import the
whole &lt;code&gt;HttpModule&lt;/code&gt; and override &lt;code&gt;ConnectionBackend&lt;/code&gt; or just provide both
directly. Things are much simpler with the &lt;code&gt;HttpClientTestingModule&lt;/code&gt;, however,
which can be added to the &lt;code&gt;imports&lt;/code&gt; array and will mock everything for you:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configureTestingModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;imports&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;HttpClientTestingModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;PersonService&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PersonService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;httpMock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HttpTestingController&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;httpMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now instead of the &lt;code&gt;MockBackend&lt;/code&gt; we get access to the &lt;code&gt;HttpTestingController&lt;/code&gt;,
which gives access to a more synchronous API; rather than subscribing to a
stream of connections you can assert on the existence of specific requests,
flush them with a response as needed then verify that no unexpected requests
have been made.&lt;/p&gt;
&lt;h2&gt;Checking the request&lt;/h2&gt;
&lt;p&gt;A simple first test for a new service method would be to check that it's making
the appropriate request to the backend.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;should get the people from the API&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;MockConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\/people$/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RequestMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;expected GET request&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Rewriting this with the new test API:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;should get the people from the API&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;httpMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expectOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/app/people&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This demonstrates the value of the synchronous API; no more worrying about the
&lt;code&gt;DoneFn&lt;/code&gt; and which order to make the request and set up the expectations.
Personally I find the second version much more readable, as it's a better fit
for the common &lt;em&gt;"Arrange, Act, Assert"&lt;/em&gt; (or &lt;em&gt;"Given, When, Then"&lt;/em&gt;) testing
pattern.&lt;/p&gt;
&lt;p&gt;Sadly, one thing that seems to be missing here is the ability to provide a
regular expression to match the request URL. We have found this very useful in
combination with the Angular CLI's environment settings, allowing us to use
different profiles for local testing and our various deployment environments,
without the root URLs bleeding into the test setup.&lt;/p&gt;
&lt;p&gt;Instead, you can match just the request method to get access to the
&lt;code&gt;TestRequest&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;httpMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expectOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\/people$/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Also note that the API now uses string representations of the request method
rather than the enumerator I mentioned in &lt;a href="https://blog.jonrshar.pe/2017/Apr/16/async-angular-tests.html"&gt;&lt;em&gt;Testing async data in Angular&lt;/em&gt;&lt;/a&gt;;
&lt;em&gt;"Expected 'GET' to be 'POST'."&lt;/em&gt; is a definite improvement over &lt;em&gt;"Expected 0 to
be 1."&lt;/em&gt;, although you don't get the IDE support to autocomplete it.&lt;/p&gt;
&lt;h2&gt;Testing with response data&lt;/h2&gt;
&lt;p&gt;The next test would drive out what happens with the data in the response. As
the new client parses the JSON for you by default you may not need to do
anything further, but in this case I want the raw object converted to a class
with some business logic in it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;should expose the people as an observable&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;people&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Alice&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;salary&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cohort&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}];&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;MockConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockRespond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ResponseOptions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;people&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;people$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;received&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;received&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;people&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;people&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;people&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;cohort&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I have generally been avoiding testing the request and the response handling in
the same method because that leads to having assertions before &lt;em&gt;and&lt;/em&gt; after the
point at which the action is taken, which seems very confusing (&lt;em&gt;"Arrange &amp;amp;
Assert, Act, Assert Again"&lt;/em&gt;?) But with the new API, the assertions all come
after the action:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;should expose the people as an observable&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;people&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Alice&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;salary&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cohort&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}];&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;httpMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expectOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\/people$/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;people&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;people$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;received&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;received&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;people&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;people&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;people&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;cohort&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Given that some expectation on the request is needed to get access to the
&lt;code&gt;TestRequest&lt;/code&gt; to &lt;code&gt;flush&lt;/code&gt; the response data through it, and given that this all
happens &lt;em&gt;after&lt;/em&gt; the service call, it now seems sensible to combine two tests
into a single one.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;DoneFn&lt;/code&gt; is still required, because I'm exposing the data over an
observable as discussed in &lt;a href="https://blog.jonrshar.pe/2017/Apr/09/async-angular-data.html"&gt;&lt;em&gt;Handling data with the Angular AsyncPipe&lt;/em&gt;&lt;/a&gt;, but
the HTTP part of the test is still handled synchronously. &lt;code&gt;people$&lt;/code&gt; has replay
behaviour in this case, so we can subscribe after the service call and still
receive the latest data.&lt;/p&gt;
&lt;h2&gt;Testing a POST&lt;/h2&gt;
&lt;p&gt;The in-memory web API also provides create and delete functionality.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;should post a new person to the API&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Lynn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;salary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;123&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cohort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Q&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;MockConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\/people$/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RequestMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;expected POST request&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cohort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addPerson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cohort&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Converting this to the new testing API is reasonably straightforward again,
giving the test much clearer structure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;should post a new person to the API&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Lynn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;salary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;123&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cohort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Q&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addPerson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cohort&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;httpMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expectOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\/people$/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cohort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;One big gotcha here is that &lt;code&gt;HttpClient.post&lt;/code&gt;, unlike &lt;code&gt;Http.post&lt;/code&gt;, seems to be
a &lt;em&gt;cold&lt;/em&gt; observable; you need to &lt;code&gt;subscribe&lt;/code&gt; for the request to actually take
place. This may mean refactoring usages of these methods if you aren't already
returning and subscribing to the request observable.&lt;/p&gt;
&lt;h2&gt;Service implementation&lt;/h2&gt;
&lt;p&gt;For the POSTs and DELETEs not much changed, I just had to replace &lt;code&gt;private
http: Http&lt;/code&gt; with &lt;code&gt;private http: HttpClient&lt;/code&gt; and the code continued to work
perfectly; the generic types on request methods are optional, and if you're not
dealing with a response you can ignore them completely. Actually using the
generic types, and with the default JSON unwrapping, the &lt;code&gt;fetch&lt;/code&gt; method went
from:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;personRoute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;personSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deserialise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;httpClient&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;RawPerson&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;personRoute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;personSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deserialise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A subtle improvement, but it helps to document your expectations of the API and
means you get IDE support for the resulting object.&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;Http&lt;/code&gt; or &lt;code&gt;HttpClient&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;For new code, adopting the &lt;code&gt;HttpClient&lt;/code&gt; is a no-brainer; even &lt;a href="https://angular.io/guide/http"&gt;the
documentation&lt;/a&gt; has been switched over. So the big question is, is it worth
switching an application over to &lt;code&gt;HttpClient&lt;/code&gt;? In the application code, the
switching process is not terribly complicated; anything that doesn't involve
the response may already work. And if you &lt;em&gt;are&lt;/em&gt;, the new API will likely allow
you to remove a bunch of &lt;code&gt;.json()&lt;/code&gt; calls and benefit from better type support.&lt;/p&gt;
&lt;p&gt;The downside is that the new testing API, much as I prefer it to the previous
system, means a lot of test refactoring. And as much of your code will already
be working, using a technique like &lt;a href="http://corgibytes.com/blog/2016/09/20/refactoring-against-the-red-bar/"&gt;&lt;em&gt;"refactoring against the red bar"&lt;/em&gt;&lt;/a&gt;
means you may spend a while breaking working code to ensure that the refactored
tests will fail in a useful way. So, unless you need some of the new
functionality (the interceptors seem particularly useful), it is probably not
worth the conversion unless:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;you're still in the earlier stages of development, so you're likely to get
    the benefits in the additional services you write; or&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;you're planning to keep upgrading Angular into v5 (&lt;code&gt;@angular/http&lt;/code&gt; is
    deprecated from &lt;a href="https://github.com/angular/angular/blob/fa6b802be4c79f57aa8484fe47f0c860f1226683/CHANGELOG.md#features"&gt;5.0.0-beta.6&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See the full commit switching &lt;code&gt;salary-stats&lt;/code&gt; over to the new API &lt;a href="https://github.com/textbook/salary-stats/commit/046cfb059dd1b7e141141ef018a8ee029232221c"&gt;here&lt;/a&gt;.&lt;/p&gt;</content><category term="development"></category><category term="code"></category><category term="angular"></category><category term="tdd"></category></entry><entry><title>New to Angular 4.3: HttpClient</title><link href="https://blog.jonrshar.pe/2017/Jul/15/angular-http-client.html" rel="alternate"></link><published>2017-07-15T20:30:00+01:00</published><updated>2017-10-13T14:40:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2017-07-15:/2017/Jul/15/angular-http-client.html</id><summary type="html">&lt;p&gt;The latest version of Angular includes a new HTTP client, with a new API.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently discovered from &lt;a href="https://netbasal.com/a-taste-from-the-new-angular-http-client-38fcdc6b359b"&gt;A Taste From The New Angular HTTP Client&lt;/a&gt; (via 
&lt;a href="https://www.ng-newsletter.com/"&gt;ng-newsletter&lt;/a&gt;, which covers both Angular and AngularJS) that Angular 4.3,
which &lt;a href="http://angularjs.blogspot.co.uk/2017/07/angular-43-now-available.html"&gt;just became available&lt;/a&gt;, includes a new HTTP client. &lt;/p&gt;
&lt;p&gt;So what does it add?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;JSON by default&lt;/strong&gt; - no more &lt;code&gt;.map(response =&amp;gt; response.json())&lt;/code&gt;; unless
    you explicitly specify e.g. &lt;code&gt;{ responseType: 'text' }&lt;/code&gt; when making a
    request, the client will automatically parse the response body as JSON for
    you. You can still get the response, using &lt;code&gt;{ observe: 'response' }&lt;/code&gt;, if
    you need access to e.g. the headers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Response typing&lt;/strong&gt; - versions of the requests methods that use generic
    types are now provided, so you can supply a type for the return value:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;this.http.get&amp;lt;Thing&amp;gt;(&amp;#39;/api/thing&amp;#39;).subscribe(thing =&amp;gt; ...)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Interception&lt;/strong&gt; - the new &lt;code&gt;HttpInterceptor&lt;/code&gt; interface allows you to easily
    intercept requests and responses, without needing to extend the whole
    &lt;code&gt;Http&lt;/code&gt; class yourself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Progress reporting&lt;/strong&gt; - specify &lt;code&gt;{ reportProgress: true }&lt;/code&gt; when making a
    request, and the client will give periodic updates on the upload or download
    progress.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Client testing module&lt;/strong&gt; - now you can simply add &lt;code&gt;HttpClientTestingModule&lt;/code&gt;
    to your test bed's imports, instead of setting up the &lt;code&gt;MockBackend&lt;/code&gt;
    yourself. It also provides a new API, which looks like a more synchronous
    approach to testing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Proper error response&lt;/strong&gt; - as I mentioned in &lt;a href="https://blog.jonrshar.pe/2017/Apr/16/async-angular-tests.html"&gt;Testing async data in 
    Angular&lt;/a&gt;, it used to be tricky to test error responses (e.g. 404)
    because the &lt;code&gt;mockError&lt;/code&gt; method took an &lt;code&gt;Error&lt;/code&gt;, not a &lt;code&gt;Response&lt;/code&gt;. As a
    workaround, I showed how to create a new class that combines the two.
    However, that's now provided as part of the module:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HttpErrorResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HttpResponseBase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;implements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Hopefully this will make error handling neater and testing easier.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As I'd also been playing with &lt;a href="https://material.angular.io/"&gt;&lt;code&gt;@angular/material&lt;/code&gt;&lt;/a&gt;, I thought I'd combine 3
and 4 to create an automatically-updated progress bar, which shows the progress
of the current request. First, I had to write an interceptor:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;@Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgressInterceptor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;implements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HttpInterceptor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;progress$&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;progressSubject&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progressSubject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ReplaySubject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progress$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progressSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;intercept&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;HttpHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HttpEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reportingRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reportProgress&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reportingRequest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;HttpEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HttpEventType.Sent&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="kt"&gt;this.progressSubject.next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HttpEventType.DownloadProgress&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kt"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HttpEventType.UploadProgress&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="kt"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progressSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loaded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;100&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HttpEventType.Response&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="kt"&gt;this.progressSubject.next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This does two main things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.clone&lt;/code&gt; the request object, setting the progress reporting flag. Most of
    the objects in the client are immutable, but provide helper methods to
    create new instances with updated configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.subscribe&lt;/code&gt; to the events coming out of the handler. It switches on the
    type of the event to determine the start of the request, progress updates
    and the arrival of the response. At each step it sends updates using the 
    &lt;em&gt;"public observable, private subject"&lt;/em&gt; pattern I discussed in &lt;a href="https://blog.jonrshar.pe/2017/Apr/09/async-angular-data.html"&gt;Handling
    data with the Angular AsyncPipe&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I then wrote a simple wrapper around the &lt;a href="https://material.angular.io/components/progress-bar"&gt;&lt;code&gt;&amp;lt;md-progress-bar&amp;gt;&lt;/code&gt;&lt;/a&gt;, exposing its
basic styling while hiding the other details of the API (the &lt;a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html#string-literal-types"&gt;string literal
types&lt;/a&gt; for mode and colour are defined in but not exposed by the Angular
Material package, so I recreated them):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgressBarColor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;primary&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;accent&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;warn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgressBarMode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;determinate&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;indeterminate&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;buffer&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;query&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;@Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pgs-progress-bar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="sb"&gt;    &amp;lt;md-progress-bar [value]=&amp;quot;progressPercentage$ | async&amp;quot;&lt;/span&gt;
&lt;span class="sb"&gt;                     [color]=&amp;quot;color&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;    &amp;lt;/md-progress-bar&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgressComponent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;implements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OnInit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;@Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ProgressBarColor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;primary&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;@ViewChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MdProgressBar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;progressBar&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;MdProgressBar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;progressPercentage$&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;interceptor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ProgressInterceptor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progressPercentage$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interceptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progress$&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;indeterminate&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;determinate&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ProgressBarMode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progressBar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This puts the progress bar into "indeterminate" mode, where it just shows that
&lt;em&gt;something&lt;/em&gt; is going on, if it receives &lt;code&gt;null&lt;/code&gt; from the interceptor. If it
receives a number, that's used as the value for current progress and the bar is
switched into "determinate" mode, where it fills up from 0 to 100.&lt;/p&gt;
&lt;p&gt;Finally I wrote a module to glue the two classes together and add the actual
Angular Material progress bar:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;interceptor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgressInterceptor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;@NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;imports&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;BrowserModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;HttpClientModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;MdProgressBarModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;declarations&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ProgressComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ProgressInterceptor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;useValue&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;interceptor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;HTTP_INTERCEPTORS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;useValue&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;interceptor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;multi&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ProgressComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgressModule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that I create a single instance of the interceptor, which is then both
used in the &lt;code&gt;HTTP_INTERCEPTORS&lt;/code&gt; array and provided directly under its own name.
If I'd used &lt;code&gt;useClass: ProgressInterceptor&lt;/code&gt; then the client would have been
using a different instance from the one in the component, and the progress bar
would never get updated.&lt;/p&gt;
&lt;p&gt;Now you can drop &lt;code&gt;&amp;lt;pgs-progress-bar&amp;gt;&amp;lt;/pgs-progress-bar&amp;gt;&lt;/code&gt; anywhere into the
application and it will show the status of the latest request made through the
&lt;code&gt;HttpClient&lt;/code&gt;, with no need for any additional wiring. You can also inject the
&lt;code&gt;ProgressInterceptor&lt;/code&gt; into anything else that needs to keep track of request
progress.&lt;/p&gt;
&lt;p&gt;This is just a toy implementation (one obvious issue: what if there are two
parallel requests?) but hopefully shows what's possible with the new API. Note
that the old client (in &lt;code&gt;@angular/http&lt;/code&gt;) isn't going away just yet; the new one
is available in parallel (in &lt;code&gt;@angular/common/http&lt;/code&gt;) for the time being.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: an earlier version of this article both subscribed to &lt;em&gt;and&lt;/em&gt; 
returned the &lt;code&gt;handle&lt;/code&gt; from the interceptor - as VerSo pointed out in the 
comments (thanks!), this would mean that the request got made twice.&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="development"></category><category term="angular"></category><category term="html"></category></entry><entry><title>ngTemplateOutlet tricks</title><link href="https://blog.jonrshar.pe/2017/May/29/angular-ng-template-outlet.html" rel="alternate"></link><published>2017-05-29T15:30:00+01:00</published><updated>2017-05-29T15:30:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2017-05-29:/2017/May/29/angular-ng-template-outlet.html</id><summary type="html">&lt;p&gt;Using an Angular ngTemplateOutlet to bind child content from the parent template.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I mentioned in &lt;a href="https://blog.jonrshar.pe/2017/May/20/angular-ng-elements.html"&gt;a previous article&lt;/a&gt; that the limitations of projecting
content from a parent element to its child element, for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;&amp;lt;!-- template --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ng-content&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ng-content&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- usage --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;my-component&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;I am a paragraph.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;my-component&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- result --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;I am a paragraph.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;were that you can't repeat the projection multiple times or access properties
on the child class from the parent template. However, one of
&lt;a href="https://stackoverflow.com/users/217408/g%C3%BCnter-z%C3%B6chbauer"&gt;Günter Zöchbauer&lt;/a&gt;'s &lt;em&gt;many&lt;/em&gt; contributions to the Angular community on Stack
Overflow is &lt;a href="https://stackoverflow.com/a/37676946/3001761"&gt;this answer&lt;/a&gt; outlining how the &lt;a href="https://angular.io/docs/ts/latest/api/common/index/NgTemplateOutlet-directive.html"&gt;&lt;code&gt;ngTemplateOutlet&lt;/code&gt;&lt;/a&gt; can be
used to do this. It's somewhat more complex than simply adding an &lt;code&gt;ng-content&lt;/code&gt;
element, but allows for more complex behaviour too.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;To give a more specific case, imagine you're writing an autocomplete input,
where suggestions are fetched as the user types and shown beneath the text box.
A simple approach might define the child template like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;suggestions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;suggestion&amp;quot;&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngFor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;let suggestion of suggestions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ suggestion.name }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ suggestion.email }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is limiting, though; the structure of each suggestion is fixed, making the
component less reusable. It would be more flexible if we could define the
suggestion template in the parent component then inject each &lt;code&gt;suggestion&lt;/code&gt; into
it to provide the display values.&lt;/p&gt;
&lt;p&gt;Accessing an &lt;code&gt;ng-template&lt;/code&gt; defined in the parent template as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;@Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my-child&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ChildComponent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;@ContentChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TemplateRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parentTemplate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;suggestions&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[...];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and using an &lt;code&gt;ngTemplateOutlet&lt;/code&gt; to render it out, with the child template
defined as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;suggestions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngFor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;let suggestion of suggestions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ng-container&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngTemplateOutlet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parentTemplate; context: { $implicit: suggestion }&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ng-container&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;allows you to provide the structure of each suggestion in the parent, but using
the &lt;code&gt;suggestion&lt;/code&gt; variable defined in the &lt;code&gt;ngFor&lt;/code&gt; loop in the child:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;my-child&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ng-template&lt;/span&gt; &lt;span class="na"&gt;let-suggestion&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;suggestion&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ suggestion.name }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ suggestion.email }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ng-template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;my-child&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The key points here are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Using the &lt;a href="https://angular.io/docs/ts/latest/api/core/index/ContentChild-decorator.html"&gt;&lt;code&gt;ContentChild&lt;/code&gt;&lt;/a&gt; with &lt;a href="https://angular.io/docs/ts/latest/api/core/index/TemplateRef-class.html"&gt;&lt;code&gt;TemplateRef&lt;/code&gt;&lt;/a&gt; in the child to access
   the &lt;code&gt;ng-template&lt;/code&gt; element defined in the parent;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Providing a &lt;code&gt;context&lt;/code&gt; to inject into the template and binding the
   &lt;code&gt;suggestion&lt;/code&gt; variable to the &lt;code&gt;$implicit&lt;/code&gt; key to make that the default value
   of the context; and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Placing &lt;code&gt;let-suggestion&lt;/code&gt; on the parent's &lt;code&gt;ng-template&lt;/code&gt; element to bind that
   default value context to the name &lt;code&gt;suggestion&lt;/code&gt; in the parent (you can also
   use &lt;code&gt;let-nameInParent="nameInContext"&lt;/code&gt; to bind non-default context values;
   &lt;code&gt;let-nameInParent&lt;/code&gt; without a value is effectively shorthand for
   &lt;code&gt;let-nameInParent="$implicit"&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the above example as a Plunkr so you can play with it:&lt;/p&gt;
&lt;iframe src="https://embed.plnkr.co/QNUe6R/?show=child,preview" 
        frameborder=0 
        width="100%" 
        height="400px"&gt;
&lt;/iframe&gt;

&lt;hr&gt;
&lt;p&gt;The downside of this is that the parent needs to have a specific layout; it has
to define an &lt;code&gt;ng-template&lt;/code&gt; with the appropriate &lt;code&gt;let-&lt;/code&gt; attribute to gain access
to the child property. If you read the previous article, you may be thinking
that an &lt;code&gt;ng-template&lt;/code&gt; with an attribute on it looks exactly like the de-sugared
versions of structural directives. I thought the same thing, and wondered if I
could write a structural directive of my own that would be applied as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;my-child&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;suggestion&amp;quot;&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;suggestion&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ suggestion.name }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ suggestion.email }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;my-child&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and would generate the appropriate template in a simplified way. Sadly, it
seems like you can't access the actual template element from the directive, as
it never gets rendered. A no-op directive as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;@Directive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;[suggestion]&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SuggestionDirective&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;can&lt;/em&gt; be used to simplify the markup slightly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;my-child&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;suggestion&amp;quot;&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;suggestion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;let suggestion&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ suggestion.name }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ suggestion.email }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;my-child&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;but you still need to explicitly include the &lt;code&gt;let&lt;/code&gt;. I am yet to figure out how
to get around this (or if you even can).&lt;/p&gt;</content><category term="development"></category><category term="angular"></category><category term="html"></category></entry><entry><title>Angular's "ng-" elements</title><link href="https://blog.jonrshar.pe/2017/May/20/angular-ng-elements.html" rel="alternate"></link><published>2017-05-20T17:00:00+01:00</published><updated>2017-05-29T15:30:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2017-05-20:/2017/May/20/angular-ng-elements.html</id><summary type="html">&lt;p&gt;A quick spotters' guide to some of the "ng-" elements that Angular provides.&lt;/p&gt;</summary><content type="html">&lt;p&gt;AngularJS (1.x) had a lot of attributes that followed the &lt;code&gt;ng-&lt;/code&gt; naming
convention, but you don't see that prefix much in Angular (2+); &lt;code&gt;ng-repeat&lt;/code&gt; is
now &lt;code&gt;*ngFor&lt;/code&gt;, &lt;code&gt;ng-blur&lt;/code&gt; is &lt;code&gt;(blur)&lt;/code&gt;, etc. However, there are a few core
elements named with that prefix. They all share one common trait (they don't
get rendered into the resulting DOM) but differ in behaviour and usage. I found
it difficult to remember which was which, so wrote this out to help get them
straight in my head.&lt;/p&gt;
&lt;h2&gt;DOM simplification with &lt;code&gt;ng-container&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The one you will probably end up writing most frequently yourself is
&lt;code&gt;ng-container&lt;/code&gt;. This doesn't confer any particular behaviour, and just includes
its children in the markup wherever you put it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;&amp;lt;!-- usage --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ng-container&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;I am a paragraph.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ng-container&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- result --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;I am a paragraph.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To see where this could be useful, imagine you want to conditionally include
multiple elements without breaking a parent-child relationship between elements
(for example in a list or table, or where the relationship is used for a CSS
styling rule) or repeating the condition over and over again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;&amp;lt;!-- without ng-container: invalid markup --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; 
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;One&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Two&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngIf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;moreThanTwo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Three&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Four&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Five&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- without ng-container: repetition --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;One&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Two&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngIf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;moreThanTwo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Three&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngIf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;moreThanTwo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Four&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngIf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;moreThanTwo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Five&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- with ng-container --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; 
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;One&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Two&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ng-container&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngIf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;moreThanTwo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Three&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Four&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Five&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ng-container&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Content projection with &lt;code&gt;ng-content&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;If you ever used AngularJS (the 1.x precursor to Angular versions 2 and up),
you may have come across &lt;em&gt;"transclusion"&lt;/em&gt;, the ability to take HTML and inject
it into a template at a specified position. That is now referred to as
&lt;em&gt;"content projection"&lt;/em&gt;, but is a similar idea. In this case, the &lt;code&gt;ng-content&lt;/code&gt;
element is completely replaced by the projected content. For example, given
a component with the selector &lt;code&gt;'my-component'&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;&amp;lt;!-- template --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ng-content&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ng-content&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- usage --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;my-component&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;I am a paragraph.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;my-component&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- result --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;I am a paragraph.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The element also takes a &lt;code&gt;select&lt;/code&gt; attribute, which allows you to specify which
element gets projected where, especially when you have multiple &lt;code&gt;ng-content&lt;/code&gt;
elements for different projected content. A more complex example, using classes
as selectors:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;&amp;lt;!-- template --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ng-content&lt;/span&gt; &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;.first&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ng-content&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ng-content&lt;/span&gt; &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;.second&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ng-content&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- usage --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;my-component&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;second&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;I will be last.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;first&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;And I will be first.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;my-component&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- result --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;first&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;I will be first.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;second&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;I will be last.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There are three selectors you can use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;classes (&lt;code&gt;select=".class"&lt;/code&gt;); &lt;/li&gt;
&lt;li&gt;attributes (&lt;code&gt;select="[attribute]"&lt;/code&gt;); and&lt;/li&gt;
&lt;li&gt;elements (&lt;code&gt;select="element"&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They can be combined as needed; using all three:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;&amp;lt;!-- template --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ng-content&lt;/span&gt; &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;p.foo[aria-label]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ng-content&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- usage --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;my-component&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;foo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Ignore me.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;foo&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Projected&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Project me.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;bar&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Ignored&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Ignore me.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;my-component&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- result --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;foo&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Projected&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Project me.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This can be useful for writing generic components whose markup you'd like the
parent component to have some influence over. However, note that you can't do
e.g. &lt;code&gt;&amp;lt;ng-content *ngFor="..."&amp;gt;&lt;/code&gt;, as the content is only ever projected once,
and the projected content from the parent can't reference variables in the
child.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that you &lt;em&gt;can&lt;/em&gt; pass down a context if you use a template outlet to
render the child content, see &lt;a href="https://blog.jonrshar.pe/2017/May/29/angular-ng-template-outlet.html"&gt;ngTemplateOutlet tricks&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;&amp;lt;!-- template --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;child&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ childProperty }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ng-content&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ng-content&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- usage --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;my-component&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ childProperty }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ parentProperty }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;my-component&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- result --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;child&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;I am a property of the child.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;I am a property of the parent.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Conditional inclusion with &lt;code&gt;ng-template&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The last element you will probably be using very frequently even if you don't
ever write it yourself, because it's generated by Angular for &lt;a href="https://angular.io/docs/ts/latest/guide/structural-directives.html"&gt;structural
directives&lt;/a&gt; like &lt;code&gt;*ngIf&lt;/code&gt; and &lt;code&gt;*ngFor&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;ng-template&lt;/code&gt; is a bit like &lt;code&gt;ng-container&lt;/code&gt;, in that only its children get
included in the rendered DOM, but it has conditional behaviour; by default,
you'll just see comments in the markup, for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;&amp;lt;!--bindings={&lt;/span&gt;
&lt;span class="cm"&gt;  &amp;quot;ng-reflect-ng-if&amp;quot;: &amp;quot;true&amp;quot;&lt;/span&gt;
&lt;span class="cm"&gt;}--&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!--bindings={&lt;/span&gt;
&lt;span class="cm"&gt;  &amp;quot;ng-reflect-ng-switch-case&amp;quot;: &amp;quot;true&amp;quot;&lt;/span&gt;
&lt;span class="cm"&gt;}--&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!--bindings={&lt;/span&gt;
&lt;span class="cm"&gt;  &amp;quot;ng-reflect-ng-for-of&amp;quot;: &amp;quot;[object Object],[object Object&amp;quot;&lt;/span&gt;
&lt;span class="cm"&gt;}--&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;These bindings are the rules that tell Angular what to render and under what
circumstances. If the appropriate conditions aren't met, the relevant elements
won't be included at all. Structural directives are de-sugared into
&lt;code&gt;ng-template&lt;/code&gt; elements in two steps:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;&amp;lt;!-- original --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngIf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;condition&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Maybe show me?&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- step 1 --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ngIf condition&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Maybe show me?&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- step 2 --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ng-template&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="na"&gt;ngIf&lt;/span&gt;&lt;span class="err"&gt;]=&amp;quot;&lt;/span&gt;&lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="err"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Maybe show me?&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ng-template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can see, this turns the syntactic sugar into an &lt;code&gt;ng-template&lt;/code&gt; element
with the familiar &lt;code&gt;[...]="..."&lt;/code&gt; property binding.&lt;/p&gt;
&lt;p&gt;If you're using Angular 4.x, you need to use &lt;code&gt;ng-template&lt;/code&gt; directly for the new
&lt;code&gt;then&lt;/code&gt; and &lt;code&gt;else&lt;/code&gt; syntax on &lt;code&gt;ngIf&lt;/code&gt;, so your alternative elements don't appear
in the rendered DOM by default. Below are four ways to show one of two elements
depending on a boolean property:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;&amp;lt;!-- 2.x version with ngSwitch --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="na"&gt;ngSwitch&lt;/span&gt;&lt;span class="err"&gt;]=&amp;quot;&lt;/span&gt;&lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="err"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngSwitchCase&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Condition was true.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngSwitchDefault&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Condition was false.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- 2.x version with ngIf twice --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngIf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;condition&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Condition was true.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngIf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;!condition&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Condition was false.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- 4.x version with ngIf; else --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngIf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;condition; else falseCase&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Condition was true.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ng-template&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="na"&gt;falseCase&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Condition was false.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ng-template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- 4.x version with ngIf; then; else --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngIf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;condition; then trueCase; else falseCase&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ng-template&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="na"&gt;trueCase&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Condition was true.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ng-template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ng-template&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="na"&gt;falseCase&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Condition was false.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ng-template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- result (condition=true) --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Condition was true.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="development"></category><category term="angular"></category><category term="html"></category></entry><entry><title>Eleanor Rigbot</title><link href="https://blog.jonrshar.pe/2017/May/14/eleanor-rigbot.html" rel="alternate"></link><published>2017-05-14T13:00:00+01:00</published><updated>2017-05-14T13:00:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2017-05-14:/2017/May/14/eleanor-rigbot.html</id><summary type="html">&lt;p&gt;My first Twitter bot, searching Liverpool's tweet stream for new verses to Eleanor Rigby&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;Eleanor Rigby, picks up the rice &lt;/p&gt;
&lt;p&gt;In the church where a wedding has been&lt;/p&gt;
&lt;p&gt;Lives in a dream&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;strong&gt;Eleanor Rigby&lt;/strong&gt;, by the Beatles (&lt;em&gt;Lennon-McCartney, 1966&lt;/em&gt;).&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The song &lt;a href="https://en.wikipedia.org/wiki/Eleanor_Rigby"&gt;Eleanor Rigby&lt;/a&gt; has a distinctive syllabic pattern and rhyming
scheme, and it's fun to try and come up with additional verses. But what if we
could find them automatically, instead?&lt;/p&gt;
&lt;p&gt;Since the people behind Clitoris Vulgaris (&lt;a href="https://twitter.com/clitoscope"&gt;@clitoscope&lt;/a&gt;), a Twitter bot
designed to &lt;em&gt;"generate new species of clitoris by projecting botanical
illustrations onto a 3D model"&lt;/em&gt;, gave a talk at work about how they built it,
I've been interested in the idea of making a bot of my own. Creating something
to look for tweets that could be a new verse seemed doable as a first attempt.&lt;/p&gt;
&lt;h2&gt;Libraries&lt;/h2&gt;
&lt;p&gt;I'd initially looked at using &lt;a href="http://www.nltk.org/index.html"&gt;NLTK&lt;/a&gt; to do the language processing; one
corpus available for it, the Carnegie Mellon Pronouncing Dictionary, seemed
perfect for the project. However, I wasn't sure how to go about running an 
NLTK-based app on Cloud Foundry, as it needs to download the corpus once &lt;code&gt;pip
install&lt;/code&gt;-ed. Fortunately, someone else had already done the hard work for me
and wrapped that single corpus in a library named &lt;a href="https://pypi.python.org/pypi/pronouncing"&gt;&lt;code&gt;pronouncing&lt;/code&gt;&lt;/a&gt;; using
this made the bot trivial to deploy. This allows for both determining the
syllables in the words used and whether two given words rhyme.&lt;/p&gt;
&lt;p&gt;Using the &lt;a href="http://docs.tweepy.org/en/latest/"&gt;Tweepy&lt;/a&gt; library made interacting with Twitter simple; I
subclassed the existing &lt;code&gt;StreamListener&lt;/code&gt; with my own &lt;code&gt;RetweetListener&lt;/code&gt;, which
takes an &lt;code&gt;extractor&lt;/code&gt; (for extracting the text to process from a tweet) and a
&lt;code&gt;filterer&lt;/code&gt; (for filtering out tweets that should be retweeted). This keeps it
very general for reuse later. Tweets are not exactly written in formal English
and contain things like usernames, hashtags and URLs that can't necessarily be
pronounced, so the current extractor takes the longest string of &lt;em&gt;"clean
characters"&lt;/em&gt; (the letters A-Z plus some basic punctuation characters, not
including characters like &lt;code&gt;@&lt;/code&gt; and &lt;code&gt;#&lt;/code&gt; with Twitter-specific meanings) from each
tweet to pass to the filtering.&lt;/p&gt;
&lt;h2&gt;Classification&lt;/h2&gt;
&lt;p&gt;The actual classification happens in &lt;a href="https://github.com/textbook/eleanor-rigbot/blob/9bedfdce7def83cb13021e3751f55716d9a3a307/eleanorrigbot/classify.py#L42"&gt;&lt;code&gt;PhraseMatcher.__call__&lt;/code&gt;&lt;/a&gt;, and the
overall process is pretty straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Given a phrase string, e.g. &lt;code&gt;'here are some example words'&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Create a list of the individual words and their lengths in syllables, e.g.
    &lt;code&gt;[('here', 1), ('are', 1), ('some', 1), ('example', 3), ('words', 1)]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If any word couldn't be processed or the total syllable count doesn't match
    the pattern we're looking for, return &lt;code&gt;False&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Try to fit the words into the required syllable pattern, assuming that we
    want lines to break on words;&lt;/li&gt;
&lt;li&gt;If the phrase doesn't fit into the syllable pattern, return &lt;code&gt;False&lt;/code&gt;; and&lt;/li&gt;
&lt;li&gt;Return whether or not the phrase matches the required rhyming scheme.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The scheme is defined with two sequences: one of the number of syllables in
each line; and one of the required rhyming scheme. For example, the scheme to
match a verse of Eleanor Rigby is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;ELEANOR_RIGBY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PhraseMatcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;syllable_pattern&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;rhyming_scheme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;there should be four lines, with five, four, nine and four syllables
   respectively (for a total of 22); and&lt;/li&gt;
&lt;li&gt;the third and fourth lines must rhyme (but we don't care whether or not the
   first two lines rhyme with anything).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This representation makes the bot configurable for different phrases, including
the other example I give in the README (note that here the rhyme scheme is
marked with letters - it gets converted to a dictionary mapping group ID to
line indices, so anything hashable is fine):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# https://www.flickr.com/places/info/12591829&lt;/span&gt;
&lt;span class="n"&gt;napoli&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;13.8509&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;40.5360&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;14.6697&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;41.0201&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# &amp;quot;when the moon hits your eye like a big pizza pie&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;that_is_amore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PhraseMatcher&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;start_listening&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;napoli&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;that_is_amore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;The very first retweet is probably still the best so far:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It's kinda shitty when people say they care then act like they don't give
two shits about you&lt;/p&gt;
&lt;p&gt;&lt;small&gt;Charley Emmett (@Ch4rl3y_B0y) &lt;a href="https://twitter.com/Ch4rl3y_B0y/status/850067453761773569"&gt;April 6, 2017&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;which becomes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It's kinda shitty, when people say&lt;/p&gt;
&lt;p&gt;They care then act like they don't give two&lt;/p&gt;
&lt;p&gt;Shits about you&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Aside from an overly-strict definition of rhyming (some rhymes from the actual
song, like &lt;em&gt;"grave"&lt;/em&gt; and &lt;em&gt;"saved"&lt;/em&gt;, aren't considered valid), the
classification seems to be working pretty effectively. The extraction is not as
good; it doesn't necessarily find the real content of the status correctly. For
example, one of the weakest matches so far is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Away to &lt;a href="https://twitter.com/NewportSarries"&gt;@NewportSarries&lt;/a&gt; tomorrow &lt;/p&gt;
&lt;p&gt;End of the season is in sight &lt;/p&gt;
&lt;p&gt;And it's going to be a glorious day 🌞just what you want as a front row&lt;/p&gt;
&lt;p&gt;&lt;small&gt;Malpas RFC (@MalpasRugbyClub) &lt;a href="https://twitter.com/MalpasRugbyClub/status/850270568066588672"&gt;April 7, 2017&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is being classified as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tomorrow end of, the season is&lt;/p&gt;
&lt;p&gt;In sight and it's going to be a&lt;/p&gt;
&lt;p&gt;Glorious day&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Most of the content of the tweet is being discarded, so it's not obvious from
reading it where the verse is.&lt;/p&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;A few improvements I've thought of:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;To make it more obvious what the matched "verse" is, I could switch from
    retweeting to quoting, so the bot can include the match in its response.
    However, that might make it seem more invasive.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Instead of the separate extraction and classification steps, the extraction
    could be based on the longest series of pronounceable words in the tweet.
    This would mean using the default no-op &lt;code&gt;_all_text&lt;/code&gt; extractor in the 
    &lt;code&gt;RetweetListener&lt;/code&gt; and moving the extraction into the &lt;code&gt;PhraseMatcher&lt;/code&gt; class.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It might be nice to include pronounceable user names and hashtags in the
    match, although splitting those with multiple words in may prove tricky.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href="https://commons.wikimedia.org/wiki/File:Eleanor_Rigby_-_geograph.org.uk_-_1457024.jpg#/media/File:Eleanor_Rigby_-_geograph.org.uk_-_1457024.jpg"&gt;&lt;img alt="Eleanor Rigby - geograph.org.uk - 1457024.jpg" src="https://upload.wikimedia.org/wikipedia/commons/d/d7/Eleanor_Rigby_-_geograph.org.uk_-_1457024.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;small&gt;By john driscoll, &lt;a href="http://creativecommons.org/licenses/by-sa/2.0" title="Creative Commons Attribution-Share Alike 2.0"&gt;CC BY-SA 2.0&lt;/a&gt;, &lt;a href="https://commons.wikimedia.org/w/index.php?curid=14199222"&gt;Link&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</content><category term="development"></category><category term="code"></category><category term="python"></category><category term="twitter"></category></entry><entry><title>Testing async data in Angular</title><link href="https://blog.jonrshar.pe/2017/Apr/16/async-angular-tests.html" rel="alternate"></link><published>2017-04-16T15:00:00+01:00</published><updated>2021-09-11T10:30:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2017-04-16:/2017/Apr/16/async-angular-tests.html</id><summary type="html">&lt;p&gt;Using the Angular TestBed to test asynchronous API data manipulation.&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;This post is a follow up to &lt;a href="https://blog.jonrshar.pe/2017/Apr/09/async-angular-data.html"&gt;Handling data with the Angular AsyncPipe&lt;/a&gt;, 
and assumes you're familiar with the service and component used there.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In a previous article I outlined a way to use the asynchronous nature of 
&lt;code&gt;Http&lt;/code&gt;-based services to handle streams of data right through to the template. 
One of the core practices of the Extreme Programming methodology we use at 
Pivotal is test-driven development (TDD), so I thought it would also be helpful 
to show how we've approached writing our tests.&lt;/p&gt;
&lt;p&gt;Angular comes with some useful built-in functionality to enable testing; see 
&lt;a href="https://angular.io/docs/ts/latest/guide/testing.html"&gt;their article&lt;/a&gt; on the subject for more information. We're also using 
Jasmine's &lt;a href="https://jasmine.github.io/2.5/introduction#section-Asynchronous_Support"&gt;asynchronous support&lt;/a&gt;, calling &lt;code&gt;done()&lt;/code&gt; to explicitly define when
a test is considered finished.&lt;/p&gt;
&lt;h2&gt;Testing the service&lt;/h2&gt;
&lt;p&gt;Angular includes a &lt;a href="https://angular.io/docs/ts/latest/api/http/testing/index/MockBackend-class.html"&gt;&lt;code&gt;MockBackend&lt;/code&gt;&lt;/a&gt; that we can inject into the &lt;code&gt;Http&lt;/code&gt;
created for the service to give access to the connections it's creating,
exposing an interface for testing and isolating tests from the actual network.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;RandomUserService&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;RandomUserService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;MockBackend&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configureTestingModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ConnectionBackend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;useClass&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;MockBackend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;RequestOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;useClass&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;BaseRequestOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;RandomUserService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RandomUserService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ConnectionBackend&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;should make a GET to the API on fetch&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;MockConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://randomuser.me/api/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RequestMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;expected GET request&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetchRandomUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;should expose the first result from the response&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;expectedUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Alice&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;MockConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockRespond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ResponseOptions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;expectedUser&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;})));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetchRandomUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;randomUser$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This allows us to test both:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;That the request is correct&lt;/em&gt;: we can check the URL, request method and 
    other properties to ensure that the settings are correct. Note the 
    non-default message on the method assertion; &lt;code&gt;RequestMethod&lt;/code&gt; is an enum, 
    and e.g. &lt;em&gt;"Expected 0 to be 1."&lt;/em&gt; isn't a very useful failure message.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;That the response handling is correct&lt;/em&gt;: in this case, that the first
    entry in the response JSON's &lt;code&gt;results&lt;/code&gt; value is exposed over the 
    observable.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note another advantage of the subject/observable formulation over the original
version here; as the subscription to &lt;code&gt;http.get(...)&lt;/code&gt; happens inside the fetch
method, you don't need to subscribe to the result in the first test, where the
response is irrelevant. In cases where the request observable is returned from 
the service, &lt;em&gt;no request is made&lt;/em&gt; unless the caller subscribes to it; GET is a
cold observable (however e.g. POST and PUT are hot, so you don't need to
subscribe unless you are actually interested in the result).&lt;/p&gt;
&lt;h2&gt;Testing the component&lt;/h2&gt;
&lt;p&gt;As components include templates, which must be compiled, the testing is 
slightly more complex. The compilation is asynchronous, so the 
&lt;a href="https://cli.angular.io/"&gt;Angular CLI&lt;/a&gt; creates components with a test setup like the following: two
&lt;code&gt;beforeEach&lt;/code&gt; calls, one with &lt;code&gt;async&lt;/code&gt; to run the compilation, then a second
synchronous call where the fixture is created.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;RandomUserComponent&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;RandomUserComponent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ComponentFixture&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RandomUserComponent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;serviceSpy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;RandomUserService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userSubject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ReplaySubject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;serviceSpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jasmine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createSpyObj&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;RandomUserService&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;fetchRandomUser&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;serviceSpy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;randomUser$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configureTestingModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;RandomUserService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;useValue&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;serviceSpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;declarations&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;RandomUserComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;compileComponents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RandomUserComponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;should fetch and render a random user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Alice&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;userSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;firstName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceSpy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetchRandomUser&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;span&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that the subject/observable usage in the test mirrors that in the actual
service implementation; this means that new data can be pushed into the test at 
any time. You can also do this when setting up tests for the original version
of the service:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jasmine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createSpyObj&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;RandomUserService&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;getRandomUser&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getRandomUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Spy&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;returnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In both cases making the &lt;code&gt;service&lt;/code&gt; explicitly typed as a &lt;code&gt;RandomUserService&lt;/code&gt; 
means that the IDE and compiler can tell us if the wrong field and method names 
are used; e.g. if I'd mistyped the assignment of the observable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;TS2339&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;randomUsers$&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;does&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exist&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;RandomUserService&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Testing more complex components&lt;/h2&gt;
&lt;p&gt;The nice things about the &lt;code&gt;TestBed&lt;/code&gt; and &lt;code&gt;ComponentFixture&lt;/code&gt; model are that it 
allows us to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Be very specific about the dependencies of our components&lt;/em&gt;: its hooks into
    dependency injection system allow us to provide test doubles as required, 
    and either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;explicitly provide required sub-components (real or fake) to test 
   interactions; or &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;use the &lt;code&gt;NO_ERRORS_SCHEMA&lt;/code&gt; to ignore missing sub-components and test a 
   single component in isolation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Test interactions between the class and template&lt;/em&gt;: it exposes the 
    interface between the two parts of the component. For example, imagine the 
    following component:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;@Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;fetch-trigger&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;button (click)=&amp;quot;triggerFetch()&amp;quot;&amp;gt;&amp;lt;/button&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FetchTriggerComponent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;RandomUserService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;triggerFetch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetchRandomUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It's pretty straightforward to unit test that calling the &lt;code&gt;triggerFetch&lt;/code&gt; 
method invokes the appropriate service method, but given a correctly 
configured &lt;code&gt;TestBed&lt;/code&gt; you can also test that clicking the button in the HTML 
calls &lt;code&gt;triggerFetch&lt;/code&gt;. Better still, to write a test less tied to the 
current implementation, you can test &lt;em&gt;across&lt;/em&gt; the boundary that clicking 
the button calls &lt;code&gt;fetchRandomUser&lt;/code&gt; on a stub of the service.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;should fetch a random user when the button is clicked&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;button&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceSpy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetchRandomUser&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Testing service error handling&lt;/h2&gt;
&lt;p&gt;One gotcha we've come across is with handling 4xx and 5xx response status codes 
in &lt;code&gt;Http&lt;/code&gt;-based services. Introducing error handling into the component is as 
simple as providing the second callback to subscribe:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;instanceof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errorSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Naïvely, you might think that testing it is a simple matter of responding from 
the mock backend with an error code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;should expose errors&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;MockConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ResponseOptions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetchRandomUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(Note that, as we're not using a &lt;code&gt;ReplaySubject&lt;/code&gt; for errors to avoid 
replaying them after the fact, you need to &lt;code&gt;.subscribe&lt;/code&gt; &lt;/em&gt;before&lt;em&gt; triggering the 
response.)&lt;/em&gt; &lt;/p&gt;
&lt;p&gt;However, this will end with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;was&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;invoked&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;within&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;specified&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jasmine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DEFAULT_TIMEOUT_INTERVAL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Instead, you may try to use another method on the connection: &lt;code&gt;mockError&lt;/code&gt;. This 
time the TypeScript compiler has something to say:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;TS2345&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Argument&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Response&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;assignable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Error&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It turns out that, unlike &lt;code&gt;mockRespond&lt;/code&gt; and &lt;code&gt;mockDownload&lt;/code&gt;, the &lt;code&gt;mockError&lt;/code&gt; 
method takes an &lt;code&gt;Error&lt;/code&gt; rather than a &lt;code&gt;Response&lt;/code&gt;. In practice, however, the
&lt;code&gt;error&lt;/code&gt; on the second subscribe callback is &lt;code&gt;any&lt;/code&gt;, and will be a &lt;code&gt;Response&lt;/code&gt;
in the case of a 4xx or 5xx response code. To get around this, you can adopt
the suggestion from &lt;a href="https://github.com/angular/angular/pull/8961#issuecomment-251549757"&gt;this comment on the Angular GitHub repo&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MockError&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;implements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ne"&gt;Error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;which allows you to call &lt;code&gt;mockError&lt;/code&gt; and still test for &lt;code&gt;Response&lt;/code&gt; in the error
callback in the service:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;should expose errors&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;MockConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MockError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ResponseOptions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetchRandomUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There are a few open issues related to this behaviour, so hopefully it will be 
fixed at some point in the near future.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: as of 4.3.0 the new &lt;code&gt;HttpClient&lt;/code&gt; module seems to deal with this
more neatly, providing an official equivalent of &lt;code&gt;MockError&lt;/code&gt;; see &lt;a href="https://blog.jonrshar.pe/2017/Jul/15/angular-http-client.html"&gt;New to
Angular 4.3: HttpClient&lt;/a&gt; for more information.&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="development"></category><category term="code"></category><category term="angular"></category><category term="typescript"></category><category term="rxjs"></category><category term="tdd"></category></entry><entry><title>Handling data with the Angular AsyncPipe</title><link href="https://blog.jonrshar.pe/2017/Apr/09/async-angular-data.html" rel="alternate"></link><published>2017-04-09T15:20:00+01:00</published><updated>2017-04-16T15:00:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2017-04-09:/2017/Apr/09/async-angular-data.html</id><summary type="html">&lt;p&gt;Patterns for manipulating API data asynchronously using RxJS Observables in Angular.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In my current project at work, we're using &lt;a href="https://angular.io/"&gt;Angular&lt;/a&gt; to build the front end 
of a web application that gives the user a dashboard of useful information. As 
part of this we've adopted a few patterns for handling data that I thought 
would be useful to others. &lt;/p&gt;
&lt;h2&gt;Asynchronous data sources&lt;/h2&gt;
&lt;p&gt;Say we want to get a random person from &lt;a href="https://randomuser.me"&gt;randomuser.me&lt;/a&gt;, so that we can show 
their first name. We might start with a service that looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;@Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RandomUserService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;getRandomUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://randomuser.me/api/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In our component we then &lt;code&gt;.subscribe&lt;/code&gt; to the resulting observable and assign 
the data to a field:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;@Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;random-user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;span&amp;gt;{{ user.name.first }}&amp;lt;/span&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RandomUserComponent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;RandomUserService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getRandomUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;However, you quickly run into issues; prior to the GET call resolving via the 
subscription, &lt;code&gt;this.user&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt;, so getting the appropriate fields from it 
fails:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;TypeError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cannot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and the view doesn't render at all. &lt;/p&gt;
&lt;h2&gt;Simple solutions&lt;/h2&gt;
&lt;p&gt;To avoid this, you could set a default value, so that the appropriate fields 
always exist:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Alice&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;However, it's not always obvious what an appropriate default would be, and if 
the request fails the end user is potentially stuck looking at some dummy data. &lt;/p&gt;
&lt;p&gt;Alternatively we can use &lt;a href="https://angular.io/docs/ts/latest/guide/template-syntax.html#!%23safe-navigation-operator"&gt;the safe navigation operator&lt;/a&gt; to resolve each 
field:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ user?.name?.first }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;but that's not too neat and is prone to human error.&lt;/p&gt;
&lt;h2&gt;Leveraging observables&lt;/h2&gt;
&lt;p&gt;Instead, we can use the &lt;code&gt;AsyncPipe&lt;/code&gt; to resolve the value asynchronously from 
the service:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ firstName$ | async }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(The convention of a dollar sign suffix to indicate an observable was 
apparently &lt;a href="https://cycle.js.org/basic-examples.html#basic-examples-increment-a-counter-what-is-the-convention"&gt;popularised by Cycle.js&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the component, this can be implemented as a property:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;firstName$&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;randomUser$&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the service, we've been using a &lt;em&gt;"public observable, private subject"&lt;/em&gt; 
pattern:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;@Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RandomUserService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userSubject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ReplaySubject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;randomUser$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fetchRandomUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://randomuser.me/api/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Using a &lt;code&gt;ReplaySubject&lt;/code&gt; means that new subscribers, joining after a fetch, 
still get the latest value. Keeping it private means that the subscribers can't 
push new data into it, so we know any new state must come from within the 
service itself. Any component can trigger a new request via the public 
&lt;code&gt;fetch...&lt;/code&gt; method, and all subscribers then get the newest data as it arrives. &lt;/p&gt;
&lt;h2&gt;Combining data sources&lt;/h2&gt;
&lt;p&gt;In a few cases, we want to combine multiple requests (for example, to draw a 
graph using data from two API endpoints). Initially this seemed quite tricky, 
as we wouldn't necessarily know when both requests had resolved. However, RxJS 
provides &lt;code&gt;combineLatest&lt;/code&gt; for this purpose:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;combinedData$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;combineLatest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;someData$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;otherData$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;some&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;some&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// or &amp;quot;this.combine.bind(this)&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;See e.g. &lt;a href="http://rxmarbles.com/#combineLatest"&gt;RxMarbles&lt;/a&gt; for a demonstration of what this operator does, as well 
as other operators that can be applied to your streams of data. &lt;/p&gt;
&lt;h2&gt;Gotchas&lt;/h2&gt;
&lt;p&gt;Note a few problems we've run into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ExpressionChangedAfterItHasBeenCheckedError&lt;/code&gt; on anything that passes &lt;code&gt;NaN&lt;/code&gt; 
    into &lt;code&gt;| async&lt;/code&gt;; I opened &lt;a href="https://github.com/angular/angular/issues/15721"&gt;an issue&lt;/a&gt; about this and there's &lt;a href="https://github.com/angular/angular/pull/15723"&gt;a pull 
    request&lt;/a&gt; to fix it. &lt;/li&gt;
&lt;li&gt;Exposing error states with a &lt;code&gt;ReplaySubject&lt;/code&gt; can lead to some weird 
    behaviour; use a vanilla &lt;code&gt;Subject&lt;/code&gt; instead, to avoid errors getting 
    replayed later. &lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;For more information on testing services and components written in this way, 
see the follow-up article &lt;a href="https://blog.jonrshar.pe/2017/Apr/16/async-angular-tests.html"&gt;Testing async data in Angular&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="development"></category><category term="code"></category><category term="angular"></category><category term="typescript"></category><category term="rxjs"></category></entry><entry><title>Interviewing and hiring at Pivotal</title><link href="https://blog.jonrshar.pe/2016/Dec/05/pivotal-interviews.html" rel="alternate"></link><published>2016-12-05T21:11:00+00:00</published><updated>2021-11-24T11:12:00+00:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2016-12-05:/2016/Dec/05/pivotal-interviews.html</id><summary type="html">&lt;p&gt;My experiences of the hiring process at Pivotal, from both sides.&lt;/p&gt;</summary><content type="html">&lt;p&gt;My current employer, Pivotal, practices the Extreme Programming (XP) software
development methodology. One of the core practices of this methodology is pair
programming: two engineers, two mice, two keyboards, two monitors but &lt;em&gt;one
computer&lt;/em&gt; and &lt;em&gt;one task&lt;/em&gt;.  Here's what a typical day of pairing looks like:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pairing at Pivotal London" src="https://blog.jonrshar.pe/images/springboard-pairing.png"&gt;&lt;/p&gt;
&lt;p&gt;I won't go over the various methods for pairing here, but suffice to say
this style of development requires solid interpersonal skills, even before we
get into the &lt;a href="https://blog.pivotal.io/pivotal-labs/tech-talks/balanced-team-janice-fraser"&gt;balanced team&lt;/a&gt; approach that sits the engineers with the
product managers and designers to collaborate together on a daily basis.&lt;/p&gt;
&lt;p&gt;In what I think is a nice summary of this approach, there was a poster in
Pivotal London's old office featuring Pivotal Labs founder and current Pivotal
CEO Rob Mee with the quote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Software development, it turns out, is a team sport."&lt;/p&gt;
&lt;p&gt;&lt;small&gt;from &lt;a href="http://fourhourworkweek.com/2011/06/07/whats-your-start-up-bus-count-7-myths-of-entrepreneurship-and-programming/"&gt;What's Your Start-up's "Bus Count"? 7 Myths of Entrepreneurship 
and Programming&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;All of this means that there are plenty of great programmers out there who would
not be a great fit for this working method, preferring to work exclusively on
their own. Conversely, there are plenty of people who might not think that they
are great programmers who have all of the skills needed to flourish as an
engineer at Pivotal. As &lt;a href="https://blog.pivotal.io/pivotal/features/pivots-are-what-set-pivotal-apart"&gt;Pivots Are What Set Pivotal Apart&lt;/a&gt; puts it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;...being a Pivot means embracing the principles at the core of this approach.
Pivots teach and learn from each other, demonstrating empathy, trust,
respect, communication, feedback, and collaboration at every turn.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Pivotal hires, in short, for &lt;em&gt;empathy&lt;/em&gt;. So how do they do that? There is a
three-step process for hiring a new engineer at Pivotal:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Initial CV/cover letter screening or contact from a recruiter.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A short (30-60min) technical screening known as the RPI (for &lt;em&gt;"Rob [Mee]
Pairing Interview"&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;This is a simple pair programming exercise where the Pivotal employee
"drives" (i.e. does all of the typing) while the candidate "navigates" (i.e.
figures out what the driver should type next). This is to demonstrate that
the candidate knows the basics of writing code and also that they're capable
of interacting with another engineer while doing so, describing their
thinking.&lt;/p&gt;
&lt;p&gt;I found this a really good introduction to how engineering at Pivotal works,
and the format makes it fairly low-pressure. By making the Pivotal employee
the primary driver, it ensures that you can complete the exercise whether or
not you're familiar with the specific language used (I wasn't); all that's
needed is an ability to express the underlying ideas and engage in a
discussion around the best way to accomplish the task.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A &lt;em&gt;"day in the life"&lt;/em&gt; session at a Pivotal office.&lt;/p&gt;
&lt;p&gt;The final step is a full-day exercise, which generally involves two half-day
pairing sessions, one with an engineer from the &lt;a href="https://pivotal.io/platform"&gt;Cloud Foundry&lt;/a&gt; side of
the business and one with an engineer from the &lt;a href="https://pivotal.io/labs"&gt;Labs&lt;/a&gt; side (the hiring
criteria are the same, I know quite a few people who've moved from one to
the other). Where possible, the candidate works on real projects and solves
real problems (or fails to, which doesn't necessarily mean failing the
interview!) The idea is to make it as representative as possible of what
working for Pivotal is like; this is as much for the candidate to get a
sense of whether they'd like it as the other way around.&lt;/p&gt;
&lt;p&gt;Although a full day of pair programming can be exhausting, especially if you
are new to it (as I was when I interviewed), I felt that the level of
support you got from your pair made up for that. Again, if you aren't
familiar with the specific technologies being used, they can do more of the
driving while you help with suggestions on the direction the task is taking.
As with the RPI there isn't too much pressure on you, unlike the typical
tech interview, and you're actually demonstrating the skills you'll be using
on a daily basis if you join the company.&lt;/p&gt;
&lt;p&gt;I have hosted a few interviews too, which entails introducing the candidate
at the morning's office standup, taking them out for lunch and collecting
any feedback they have at the end. One candidate recently described it to me
as &lt;em&gt;"the way [they] would want to be interviewed"&lt;/em&gt;, which is heartening.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In all, I think it's a good approach that really reinforces the way that Pivotal
works; it has certainly lead to great teams at the Pivotal offices that I've
worked in (London and Dublin). By focusing on the skills you'll be using
day-to-day rather than contrived whiteboard challenges or trick questions, it
gives applicants from a broad range of backgrounds the chance to find out
whether they would enjoy the work, while acknowledging the truth that skills in
a specific programming language are often less important than the ability to
pick up the one best suited to the task. In my view, it's a truly empathetic
way to hire people.&lt;/p&gt;</content><category term="work"></category><category term="pivotal"></category><category term="hiring"></category></entry><entry><title>Meta recursion: meta-post about meta-tags</title><link href="https://blog.jonrshar.pe/2016/Dec/04/meta-meta-tags.html" rel="alternate"></link><published>2016-12-04T12:00:00+00:00</published><updated>2016-12-04T12:00:00+00:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2016-12-04:/2016/Dec/04/meta-meta-tags.html</id><summary type="html">&lt;p&gt;Integrating OpenGraph/Twitter meta-tags into a Pelican blog theme.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Social media networks like Twitter, Facebook and LinkedIn support the use of
&lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tags in your HTML to provide additional information when displaying
links to websites. Facebook and LinkedIn use &lt;a href="http://ogp.me/"&gt;OpenGraph&lt;/a&gt; tags; Twitter 
accepts some OpenGraph tags plus &lt;a href="https://dev.twitter.com/cards/markup"&gt;a few of its own&lt;/a&gt;. Providing these tags
allows you some customisation of how your pages are displayed when people share
links to them through these networks.&lt;/p&gt;
&lt;p&gt;I've recently added suport for these tags into &lt;a href="https://github.com/textbook/bulrush"&gt;Bulrush&lt;/a&gt;, the theme currently
used on this blog. What this means is that if you drop the link to one of my
articles, e.g. &lt;code&gt;http://blog.jonrshar.pe/2015/Jul/06/context-manager-case.html&lt;/code&gt;,
into &lt;a href="https://cards-dev.twitter.com/validator"&gt;the Twitter Cards validator&lt;/a&gt;, you will see something like the
following:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Twitter Cards Preview" src="https://blog.jonrshar.pe/images/twitter-card-preview.png"&gt;&lt;/p&gt;
&lt;p&gt;You may wonder how this is implemented. I'm using &lt;a href="http://jinja.pocoo.org/docs/dev/templates/#template-inheritance"&gt;template inheritance&lt;/a&gt;,
provided by the Jinja2 template engine, to allow child templates to override
their parents via &lt;code&gt;block&lt;/code&gt; and &lt;code&gt;extends&lt;/code&gt; elements, and &lt;a href="http://jinja.pocoo.org/docs/dev/templates/#include"&gt;the &lt;code&gt;include&lt;/code&gt;
statement&lt;/a&gt; to bring in the defined tags.&lt;/p&gt;
&lt;p&gt;There's a single file representing the meta tags (&lt;code&gt;meta_tags.html&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;og:title&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ SITENAME }}{% if item.title %} - {{ item.title }}{% endif %}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
{% if item.summary %}
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;og:description&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ item.summary | striptags | truncate(200, end=&amp;#39;...&amp;#39;) }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
{% endif %}
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;og:url&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ SITEURL }}/{{ item.url }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
{% if AVATAR %}
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;og:image&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ SITEURL }}/images/{{ AVATAR }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;twitter:image:alt&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ SITENAME }}{% if SITESUBTITLE %} | {{ SITESUBTITLE }}{% endif %}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
{% endif %}
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;twitter:card&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;summary&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
{% if TWITTER_USERNAME %}
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;twitter:creator&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;@{{ TWITTER_USERNAME }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;twitter:site&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;@{{ TWITTER_USERNAME }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
{% endif %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Some of the interpolated variables, like &lt;code&gt;SITENAME&lt;/code&gt;, are set in the root
&lt;code&gt;pelicanconf.py&lt;/code&gt; and made available everywhere in the templates. The &lt;a href="http://jinja.pocoo.org/docs/dev/extensions/#with-statement"&gt;&lt;code&gt;with&lt;/code&gt;
statement extension&lt;/a&gt; allows the &lt;code&gt;item&lt;/code&gt; to be injected from the template
displaying the specific item I want to be tagged when they &lt;code&gt;include&lt;/code&gt; the 
sub-template, e.g. for the articles (see &lt;code&gt;article.html&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{% block tags %}
  {% with item=article %}
    {% include &amp;#39;meta_tags.html&amp;#39; %}
  {% endwith %}
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;the &lt;code&gt;item&lt;/code&gt; is the &lt;a href="http://docs.getpelican.com/en/3.6.3/themes.html#article"&gt;&lt;code&gt;article&lt;/code&gt; object&lt;/a&gt;. Similarly, for generic pages like the
About page, the injected &lt;code&gt;item&lt;/code&gt; is the &lt;a href="http://docs.getpelican.com/en/3.6.3/themes.html#page"&gt;&lt;code&gt;page&lt;/code&gt; object&lt;/a&gt;. These both have
similar properties, like &lt;code&gt;summary&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;url&lt;/code&gt;, so it's easy to plug
either of these into the general tag layout. &lt;code&gt;{% block tags %}&lt;/code&gt; simply means
that the rendered markup will "fill in" the defined block from a parent
template, in this case in &lt;code&gt;base.html&lt;/code&gt;'s &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;p&gt;Hopefully this demonstrates how useful the template inheritance functionality of
Jinja can be. You can see &lt;a href="https://github.com/textbook/bulrush/commit/78579cb3dba20c52a7f12f98a4e3cfbe2bbac051"&gt;the full commit&lt;/a&gt; on GitHub; it took under 30
lines of HTML to add automatically-generated meta-tags to the relevant pages.
If you're using Pelican to generate your blog, feel free to give Bulrush a try
and let me know what you think. Alternatively, you can use the above ideas to
add similar functionality to your own Pelican theme, or any other Jinja2-based
site.&lt;/p&gt;</content><category term="meta"></category><category term="pelican"></category><category term="code"></category></entry><entry><title>(Not) Much Ado About Nothing</title><link href="https://blog.jonrshar.pe/2015/Nov/21/not-much-ado-about-nothing.html" rel="alternate"></link><published>2015-11-21T13:00:00+00:00</published><updated>2025-05-18T09:45:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2015-11-21:/2015/Nov/21/not-much-ado-about-nothing.html</id><summary type="html">&lt;p&gt;Why did I start this blog, again?&lt;/p&gt;</summary><content type="html">&lt;p&gt;My last post to this blog was on the 15th of August, which &lt;code&gt;datetime&lt;/code&gt; has just
informed me is exactly 14 weeks ago:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2015&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;datetime.timedelta(98)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="mi"&gt;98&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;7.0&lt;/span&gt;
&lt;span class="go"&gt;14.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You might assume, therefore, that nothing of note has happened in those weeks,
but that would not be accurate. The day before writing the post, I'd been to the
first part of the interview process for &lt;a href="http://pivotal.io/labs"&gt;Pivotal Labs&lt;/a&gt; in London, as
an eventual result of which I'm sat here between leaving TRL and starting my new
job as a Senior Software Engineer.&lt;/p&gt;
&lt;p&gt;Frankly, I'm rarely sure what to write &lt;em&gt;about&lt;/em&gt;. I've written a couple of posts
on the process of building this blog, which was an interesting process in
itself (to me, at least), but now that it's stable what do I go on to write on
it?&lt;/p&gt;
&lt;p&gt;So, in an attempt to kick myself into doing it, here are plans for a couple of
future posts that I'll try to write over the next few weeks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gig review (Tom Williams at London Fields Brewery, 2015/11/18);&lt;/li&gt;
&lt;li&gt;What Pivotal's hiring process was like;&lt;/li&gt;
&lt;li&gt;Why I'm excited to be starting at Pivotal (hopefully &lt;em&gt;before&lt;/em&gt; starting); and&lt;/li&gt;
&lt;li&gt;What starting at Pivotal was actually like (inevitably &lt;em&gt;after&lt;/em&gt; starting).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Perhaps living in London during the week will provide more interesting fodder
for this than living in Bracknell has, but we'll see!&lt;/p&gt;</content><category term="meta"></category><category term="blogging"></category><category term="writing"></category></entry><entry><title>Setting up static files in Django</title><link href="https://blog.jonrshar.pe/2015/Aug/15/django-static-shock.html" rel="alternate"></link><published>2015-08-15T18:47:00+01:00</published><updated>2015-08-15T18:47:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2015-08-15:/2015/Aug/15/django-static-shock.html</id><summary type="html">&lt;p&gt;Avoiding duplication of static files by using Mercurial hooks.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've recently been working on a project to port a web application that I wrote 
at work in .NET to Python, using the Django framework. I wanted to see what the
advantages of using a language I'm more familiar with and adopting an agile, 
test-driven development methodology would be. I'm hosting it on &lt;a href="https://www.pythonanywhere.com/"&gt;Python 
Anywhere&lt;/a&gt;, which offers a great (and free, if your needs are 
relatively small) hosting service for Python web apps.&lt;/p&gt;
&lt;p&gt;One thing to be aware of with Django is its handling of static files; there's 
a &lt;a href="https://docs.djangoproject.com/en/1.8/howto/static-files/"&gt;whole manual page&lt;/a&gt; on how to go about dealing with CSS and other
assets in testing and deployment, but the short version is that the deployed
version really needs to have all of the static files collected from their apps
(&lt;code&gt;/app/static/app/...&lt;/code&gt;) to a central directory (&lt;code&gt;/static/app/...&lt;/code&gt;). This is
easily achieved using &lt;code&gt;./manage.py collectstatic&lt;/code&gt;, but means two copies of those
files in the working copy.&lt;/p&gt;
&lt;p&gt;I already had &lt;a href="https://bitbucket.org/jonrsharpe/dj_cv/issues/3/reduce-duplication-of-static-files"&gt;an open issue&lt;/a&gt; on another Django-based project to avoid 
this kind of duplication; I used a commit hook there to run &lt;code&gt;collectstatic&lt;/code&gt; on 
the development side, then push everything, which means that there are two copies 
of the site's static files in the repository. Instead, I decided for this new 
project to move the automated static collection to the &lt;code&gt;hg update&lt;/code&gt; command (it's 
hosted on a private BitBucket repo using Mercurial, rather than Git, as I wanted 
to try out the other option). This means that it runs on the server side, and 
required two changes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Adding &lt;code&gt;^static/&lt;/code&gt; (the static file directory in the root folder, which I'd 
 set up as &lt;code&gt;STATIC_ROOT&lt;/code&gt; for the project) to the &lt;code&gt;.hgignore&lt;/code&gt;, so that its 
 contents weren't being tracked by Mercurial; and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adding an &lt;code&gt;update&lt;/code&gt; hook to &lt;code&gt;.hg/hgrc&lt;/code&gt; to perform the static file collection
 when I update the repository.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The latter looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;bash&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;collectstatic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;--noinput &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;which I think is pretty self-explanatory! Now only one copy of the CSS is kept
in the central repository, but the deployed site is set up automatically when
I pull down the changes from that repo and update the working copy.&lt;/p&gt;</content><category term="development"></category><category term="code"></category><category term="python"></category><category term="django"></category></entry><entry><title>Setting up a Travis build</title><link href="https://blog.jonrshar.pe/2015/Aug/07/build-automagic.html" rel="alternate"></link><published>2015-08-07T22:13:00+01:00</published><updated>2025-05-18T09:45:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2015-08-07:/2015/Aug/07/build-automagic.html</id><summary type="html">&lt;p&gt;Making my own life easier, one shell script at a time&lt;/p&gt;</summary><content type="html">&lt;p&gt;The one downside of using Pelican to build the site, as opposed to the Tumblr 
blog I was previously using, is that I have to &lt;em&gt;actually build the site&lt;/em&gt;. This
means that I need to have software installed to write a new article which, 
while not really a huge problem, is slightly awkward. However, I'd read a few 
things about using online automated build/continuous integration services to 
build the site for me when I push to the source repo. This means I can write a 
new article on GitHub [&lt;em&gt;Ed&lt;/em&gt;: it was right around this point that I thought &lt;em&gt;"oh 
yeah, I &lt;strong&gt;can&lt;/strong&gt; write this on GitHub!"&lt;/em&gt;, and did so], and can therefore write
from pretty much anywhere.&lt;/p&gt;
&lt;p&gt;So, I read a couple of posts about it, specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://zonca.github.io/2013/09/automatically-build-pelican-and-publish-to-github-pages.html"&gt;How to automatically build your Pelican blog and publish it to Github Pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.mathieu-leplatre.info/publish-your-pelican-blog-on-github-pages-via-travis-ci.html"&gt;Publish your Pelican blog on Github pages via Travis-CI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://kevinyap.ca/2014/06/deploying-pelican-sites-using-travis-ci/"&gt;Deploying Pelican Sites Using Travis CI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I particularly liked this approach as some of the other posts I read use 
&lt;code&gt;ghp-import&lt;/code&gt; and it seemed unnecessary to add a new dependency to the project 
when &lt;code&gt;git&lt;/code&gt; provides the tools for pretty much everything that I want. Initially
I implemented it more-or-less as-is, using &lt;code&gt;rsync&lt;/code&gt; to copy the changes from the
output folder to a fresh checkout of the deployment repo.&lt;/p&gt;
&lt;p&gt;Once I had that working, I had a bit of a rethink. I figured that there might be 
something cleverer that I could do based on the structure I'm using; because the 
&lt;code&gt;output&lt;/code&gt; folder is a submodule, it is already linked to the correct repo for 
publishing. The first problem I had is that, as I'm not now separately checking 
out the site repo, I'm relying on Travis's checkout. That isn't authenticated for
pushing, and a bit of research suggested that it isn't possible to modify the 
clone process, but you can add the correct origin back manually. Based on &lt;a href="http://stackoverflow.com/q/19845679/3001761"&gt;this 
(criminally underrated) SO post&lt;/a&gt;, I added the following to &lt;code&gt;deploy.sh&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;origin
git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;origin&lt;span class="w"&gt; &lt;/span&gt;https://&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GH_PAGES&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;@github.com/&lt;span class="nv"&gt;$TARGET_REPO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This caused my second problem, that I was now pushing to a detached &lt;code&gt;HEAD&lt;/code&gt;, so
the changes weren't actually ending up in the &lt;code&gt;master&lt;/code&gt; branch. To fix this took
&lt;a href="http://stackoverflow.com/q/5772192/3001761"&gt;another SO question&lt;/a&gt;, which suggested committing to a temporary branch, 
committing the changes then switching that back into &lt;code&gt;master&lt;/code&gt; before the push,
so I added:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;checkout&lt;span class="w"&gt; &lt;/span&gt;-b&lt;span class="w"&gt; &lt;/span&gt;temp
...
git&lt;span class="w"&gt; &lt;/span&gt;checkout&lt;span class="w"&gt; &lt;/span&gt;-B&lt;span class="w"&gt; &lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;temp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And that did it! I'm planning to set this all up as a neat Pelican starter 
project that anyone can easily fork, so keep an eye out for that if you're 
interested in setting up your own Pelican site with minimal fuss.&lt;/p&gt;</content><category term="meta"></category><category term="code"></category><category term="travis"></category><category term="pelican"></category></entry><entry><title>Switching Meadowlark to Less CSS</title><link href="https://blog.jonrshar.pe/2015/Aug/03/much-less-css.html" rel="alternate"></link><published>2015-08-03T22:00:00+01:00</published><updated>2025-05-18T09:45:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2015-08-03:/2015/Aug/03/much-less-css.html</id><summary type="html">&lt;p&gt;In which I faff around with webassets and reduce duplication&lt;/p&gt;</summary><content type="html">&lt;p&gt;As &lt;a href="https://atom.io/"&gt;Atom&lt;/a&gt;, the primary IDE I'm using to develop and maintain this site, uses
&lt;a href="http://lesscss.org/"&gt;Less CSS&lt;/a&gt; in its stylesheets, I thought I'd adopt it to reduce the style
duplication in the site's Meadowlark theme. However, this did not turn out to
be as easy as initially anticipated, so I thought I'd write down the steps in
case it helps someone else.&lt;/p&gt;
&lt;p&gt;First, an introduction: Less is one of a number of CSS preprocessors that has
popped up to address some of the features lacking in vanilla CSS, such as
defining variables and nesting styles. This allows the reduction of duplication,
while still providing backwards compatibility and not giving the browser yet
another task to do before the user gets to see the page they asked for.&lt;/p&gt;
&lt;p&gt;In case it took longer than expected to switch to Less I created a &lt;code&gt;less&lt;/code&gt;
branch in Git for both the root website repo and the &lt;code&gt;meadowlark&lt;/code&gt; submodule. I'd
already installed &lt;a href="https://webassets.readthedocs.org/en/latest/"&gt;webassets&lt;/a&gt; (&lt;code&gt;pip install webassets&lt;/code&gt;), which &lt;a href="http://docs.getpelican.com/"&gt;Pelican&lt;/a&gt; can use
to handle Less and other, similar tools (JavaScript minifiers, alternative CSS
preprocessors, &lt;em&gt;et al.&lt;/em&gt;), and Less itself (&lt;code&gt;npm install -g less&lt;/code&gt;), but I still
needed to ensure that Pelican and &lt;a href="http://jinja.pocoo.org/"&gt;Jinja&lt;/a&gt; had the appropriate extension
installed. This entailed adding another Git submodule to the project, the
&lt;a href="https://github.com/getpelican/pelican-plugins.git"&gt;&lt;code&gt;pelican-plugins&lt;/code&gt; repository&lt;/a&gt;, to get access to the &lt;code&gt;assets&lt;/code&gt; plugin.
Then I added:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;PLUGIN_PATHS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pelican-plugins&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;PLUGINS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;assets&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;JINJA_EXTENSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;webassets.ext.jinja2.AssetsExtension&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;to the &lt;code&gt;pelicanconf.py&lt;/code&gt;. This enables use of the &lt;code&gt;{% assets %}&lt;/code&gt; statement in
templates, which will be used to process the &lt;code&gt;.less&lt;/code&gt; files to &lt;code&gt;.css&lt;/code&gt; as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;assets&lt;/span&gt; &lt;span class="nv"&gt;filters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;less&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;css/main.css&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;css/main.less&amp;quot;&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="x"&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;SITEURL&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;/&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;ASSET_URL&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endassets&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This tells Jinja to take the &lt;code&gt;main.less&lt;/code&gt; file, run it through the &lt;code&gt;less&lt;/code&gt; filter,
save it as &lt;code&gt;main.css&lt;/code&gt; and use it in the template. Initially I started by
simply renaming the former &lt;code&gt;main.css&lt;/code&gt;, but once the process was working I was
able to start using Less's syntax to rearrange it; leaving, crucially, one
single, canonical definition of the three main site colours.&lt;/p&gt;
&lt;p&gt;So that's &lt;em&gt;one&lt;/em&gt; of the &lt;a href="https://blog.jonrshar.pe/2015/Jul/11/meta-meadowlark.html"&gt;things I claimed I would do&lt;/a&gt; ticked off, which
isn't bad going!&lt;/p&gt;</content><category term="meta"></category><category term="code"></category><category term="pelican"></category><category term="css"></category></entry><entry><title>Creating a static Polymer site on Divshot</title><link href="https://blog.jonrshar.pe/2015/Jul/23/polymer-and-divshot.html" rel="alternate"></link><published>2015-07-23T20:20:00+01:00</published><updated>2015-07-23T20:20:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2015-07-23:/2015/Jul/23/polymer-and-divshot.html</id><summary type="html">&lt;p&gt;An adventure in modern web development; fun with Bower, Node.JS, and more.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently acquired a new domain; having decided that &lt;code&gt;jonathansharpe.me.uk&lt;/code&gt; was
a little unwieldy, I decided to look around for something a little shorter,
settling on &lt;code&gt;http://jonrshar.pe&lt;/code&gt; (registered in Peru; &lt;a href="http://www.trashbat.co.ck/"&gt;Nathan Barley&lt;/a&gt;,
eat your heart out). However, although I wanted it primarily for email (&lt;code&gt;mail@...&lt;/code&gt;)
purposes, I thought it would be nice to have something up for people to look at
if they wandered onto the domain.&lt;/p&gt;
&lt;p&gt;Having recently become aware of &lt;a href="https://www.polymer-project.org/"&gt;Polymer&lt;/a&gt;, a Web Components library whose &lt;code&gt;paper&lt;/code&gt;
elements implement Google's &lt;a href="https://www.google.com/design/spec/material-design/introduction.html"&gt;"material design"&lt;/a&gt;, I thought I would give it
a go; getting a better grip of the front-end technologies (principally HTML,
CSS and JavaScript) is firmly on my to-do list, and it seemed easy enough to
get stuck into. Installing its components was a matter of getting &lt;a href="https://nodejs.org/"&gt;Node.js&lt;/a&gt;, then
&lt;a href="http://bower.io/"&gt;Bower&lt;/a&gt; (the de-facto web package manager), then Polymer itself. Already that's
another two things to add to my list, and that's before we get into the various
things I've (skim-)read about putting together a development workflow with Grunt
and TravisCI (which I've used on a Python project before).&lt;/p&gt;
&lt;p&gt;Writing the site itself was actually reasonably straight-forward; Polymer has
extensive documentation (although some of it's a bit of a work-in-progress)
with examples for the majority of the components. Slotting together the various
parts to build a site with a simple, one-page internal navigation system didn't
take too long, although there was a bit of back and forth and trial and (lots
of) error before it looked neat. It's a nicely modular system, at least;
although I'm not yet making best use of it, you can relatively easily create
reusable components to drop in where required. And it does look nice, a very
modern, "flat" approach you might expect from Google.&lt;/p&gt;
&lt;p&gt;Local testing also proved a little trickier than expected - now that JS is
involved I can no longer test via &lt;code&gt;file://&lt;/code&gt;, so had to get Apache properly set
up to host from &lt;code&gt;~/Sites&lt;/code&gt; That meant more tinkering with config files in &lt;code&gt;vi&lt;/code&gt;
(one day I will definitely learn more shortcuts than &lt;code&gt;!q&lt;/code&gt;, aka &lt;em&gt;"get me out of
here!"&lt;/em&gt;, honest...) and another list item (I really don't like relying on
things I don't understand, but this list's getting longer faster than I can keep
up with it!)&lt;/p&gt;
&lt;p&gt;Next I needed somewhere to host it; &lt;a href="https://divshot.com/"&gt;Divshot&lt;/a&gt; offers free static site hosting
with a neat web front-end and a command line tool that starts up like an 80s
computer game:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;_____ _______      _______ &lt;span class="ge"&gt;_    _&lt;/span&gt;  ____ _______
|  __ \_   _\ \    / / ____| |  | |/ __ \__   __|
| |  | || |  \ \  / / (___ | |__| | |  | | | |
| |  | || |   \ \/ / \___ \|  __  | |  | | | |
| |__| || |_   \  /  ____) | |  | | |__| | | |
|_____/_____|   \/  |_____/|_|  |_|\____/  |_|

Application-Grade Static Web Hosting

Host single-page apps and static sites with
all the power of a modern application platform.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It also manages multiple versions of the site, allowing a separation between the
development version and staging and live deployments, as well as easy rollbacks
in case I screw something up (which seems all-too-likely at this point).&lt;/p&gt;
&lt;p&gt;Unfortunately, after all of that, it only appears to work correctly in Google's
Chrome browser - both Safari and Firefox on my Mac refuse to let the changing
tabs actually change the displayed content (IE on my work PC won't even &lt;em&gt;show&lt;/em&gt;
the tabs, which is at least consistent!) So now I have to learn how to debug and
troubleshoot a JavaScript application - more new skills! Divshot's split between
development, staging and production versions will hopefully make it easy to play
with variants, at least. Also, I should work out how not to sync the whole of
Polymer to their servers...&lt;/p&gt;</content><category term="development"></category><category term="code"></category><category term="javascript"></category></entry><entry><title>Creating a blog in Pelican</title><link href="https://blog.jonrshar.pe/2015/Jul/11/meta-meadowlark.html" rel="alternate"></link><published>2015-07-11T16:00:00+01:00</published><updated>2015-07-11T16:00:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2015-07-11:/2015/Jul/11/meta-meadowlark.html</id><summary type="html">&lt;p&gt;The one you're reading right now, in fact!&lt;/p&gt;</summary><content type="html">&lt;p&gt;Welcome to version 1.0 of my new blog site! I thought I'd start with a bit
of a meta-post about the creating of the blog and its current &lt;code&gt;meadowlark&lt;/code&gt;
theme. A few particular comments on the process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The combination of a static site generator like Pelican and simple hosting
 like GitHub pages does make it very easy to get a site up and running - I have
 the source hosted in one repository, set to ignore the &lt;code&gt;output&lt;/code&gt; directory,
 then the actual site in &lt;code&gt;output&lt;/code&gt; in a second repo, a push on the latter and
 the live site is updated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GitHub's &lt;a href="http://atom.io/"&gt;Atom IDE&lt;/a&gt; is perfect for this level of project - I really
 like JetBrains' &lt;a href="https://www.jetbrains.com/pycharm/"&gt;PyCharm&lt;/a&gt; for projects with more substantial development, but it
 can be a little heavy for smaller projects like these, and the fact that Atom
 is based on &lt;a href="http://lesscss.org/"&gt;Less CSS&lt;/a&gt; gives me another technology to learn; and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I really do not get CSS yet - the easiest way for me to develop is way closer
 to trial and error than I'd like! Also: IE support is tricky, so I haven't
 bothered, and &lt;a href="http://wernull.com/2013/04/debug-ghost-css-elements-causing-unwanted-scrolling/"&gt;GhostCSS&lt;/a&gt; is a very handy tool for unbreaking my own
 errors.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So what's next? I have the following vague ambitions for improvements in the
coming weeks/months (&lt;em&gt;note to self: revise this to reflect whenever I get them
done!&lt;/em&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I really like the way Campo Santo's website changes colour as you sit and
 watch it - I'm thinking of using the colour to reflect the time of year/day
 (e.g. split into spring/summer/autumn/winter and morning/afternoon/evening/night
 and having a suitable colour gradient for each one);&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rethink the code block theme - I've currently just taken the basic IDLE theme
 (the IDE that comes built-in with Python), it might be nice to do something
 a bit better matched with the rest of the site theme; and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Switch the theming to Less CSS (see above), which will hopefully also make the
 above tasks easier.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content><category term="meta"></category><category term="code"></category><category term="python"></category><category term="pelican"></category></entry><entry><title>A context manager-based case statement</title><link href="https://blog.jonrshar.pe/2015/Jul/06/context-manager-case.html" rel="alternate"></link><published>2015-07-06T12:00:00+01:00</published><updated>2025-05-18T09:45:00+01:00</updated><author><name>Jonathan Sharpe</name></author><id>tag:blog.jonrshar.pe,2015-07-06:/2015/Jul/06/context-manager-case.html</id><summary type="html">&lt;p&gt;Use of Python's context manager syntax to ape a switch-case statement.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I wanted to have a post with some code in, for testing purposes, so here is a little
something I put together based on &lt;a href="http://programmers.stackexchange.com/questions/287218/i-wrote-a-python-switch-statement"&gt;this Programmers.SE question&lt;/a&gt;. &lt;code&gt;Switch&lt;/code&gt; (ab?)uses
Python's context manager &lt;code&gt;with&lt;/code&gt; statement syntax to implement a rough approximation
of the &lt;code&gt;switch&lt;/code&gt; available in some other languages.&lt;/p&gt;
&lt;p&gt;Also available &lt;a href="https://gist.github.com/textbook/5e83044f637fda1a63fe"&gt;as a Gist&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;A class for faking switch syntax with a context manager.&lt;/span&gt;

&lt;span class="sd"&gt;    Args:&lt;/span&gt;
&lt;span class="sd"&gt;      value (object): The stored value to compare any cases to.&lt;/span&gt;

&lt;span class="sd"&gt;    Example:&lt;/span&gt;

&lt;span class="sd"&gt;        &amp;gt;&amp;gt;&amp;gt; with Switch(1) as case:&lt;/span&gt;
&lt;span class="sd"&gt;        ...     if case(1):&lt;/span&gt;
&lt;span class="sd"&gt;        ...         print(&amp;#39;unity&amp;#39;)&lt;/span&gt;
&lt;span class="sd"&gt;        ...&lt;/span&gt;
&lt;span class="sd"&gt;        unity&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;gt;&amp;gt;&amp;gt; with Switch(3) as case:&lt;/span&gt;
&lt;span class="sd"&gt;        ...     if case(1):&lt;/span&gt;
&lt;span class="sd"&gt;        ...         print(&amp;#39;unity&amp;#39;)&lt;/span&gt;
&lt;span class="sd"&gt;        ...     elif case(2, 3, 4):&lt;/span&gt;
&lt;span class="sd"&gt;        ...         print(&amp;#39;small&amp;#39;)&lt;/span&gt;
&lt;span class="sd"&gt;        ...&lt;/span&gt;
&lt;span class="sd"&gt;        small&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;gt;&amp;gt;&amp;gt; with Switch(5) as case:&lt;/span&gt;
&lt;span class="sd"&gt;        ...     if case(1):&lt;/span&gt;
&lt;span class="sd"&gt;        ...         print(&amp;#39;unity&amp;#39;)&lt;/span&gt;
&lt;span class="sd"&gt;        ...     elif case(2, 3, 4):&lt;/span&gt;
&lt;span class="sd"&gt;        ...         print(&amp;#39;small&amp;#39;)&lt;/span&gt;
&lt;span class="sd"&gt;        ...     else:&lt;/span&gt;
&lt;span class="sd"&gt;        ...         print(&amp;#39;more than four&amp;#39;)&lt;/span&gt;
&lt;span class="sd"&gt;        ...&lt;/span&gt;
&lt;span class="sd"&gt;        more than four&lt;/span&gt;

&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Create a new Switch instance.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__call__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Do any of the supplied cases match the stored value?&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__enter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Enter the context manager.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__exit__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;typ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;traceback&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Don&amp;#39;t do anything when leaving the context manager.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="development"></category><category term="code"></category><category term="python"></category></entry></feed>