<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Stephen Celis blogs...</title>
  <link href="http://www.stephencelis.com/atom.xml" rel="self"/>
  <link href="http://www.stephencelis.com"/>
  <updated>2021-03-17T20:38:33+00:00</updated>
  <id>http://www.stephencelis.com</id>
  <author>
    <name>Stephen Celis</name>
    <email>stephen@stephencelis.com</email>
  </author>
  
  <entry>
    <title>Snapshot Testing in Swift</title>
    <link href="http://www.stephencelis.com/2017/09/snapshot-testing-in-swift"/>
    <updated>2017-09-13T00:00:00+00:00</updated>
    <id>http://www.stephencelis.com/2017/09/snapshot-testing-in-swift</id>
    <content type="html"><![CDATA[<figure>
  <img alt="" src="/images/snapshot-testing-in-swift/snapshot-test.png" style="max-width: 1072px" />
  <figcaption>An example of a snapshot failure in Xcode.</figcaption>
</figure>

<p>A snapshot test is a test case that uses reference data—typically a file on disk—to assert the correctness of some code.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote">1</a></sup> Where unit tests make fine-grained checks against explicit expectations (really, whatever the engineer anticipates should be right or may go wrong), snapshot tests can zoom out and cover far more surface area and potential regressions.</p>

<p>Facebook’s popular snapshot testing library, <a href="https://github.com/facebook/ios-snapshot-test-case"><code class="language-plaintext highlighter-rouge">FBSnapshotTestCase</code></a>, records screen shots of iOS views to ensure they don’t change. Writing a snapshot test is a matter of passing a view to an assertion.<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote">2</a></sup></p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="kt">FBSnapshotVerifyView</span><span class="p">(</span><span class="n">viewController</span><span class="o">.</span><span class="n">view</span><span class="p">)</span>
</code></pre></div></div>

<p>Should a screen render differently than a reference in the future, the associated test will fail and produce a diff for inspection.</p>

<figure>
  <img alt="" src="/images/snapshot-testing-in-swift/kaleidoscope-diff.png" style="max-width: 821px" />
  <figcaption>Using <a href="https://www.kaleidoscopeapp.com">Kaleidoscope</a> to investigate a screen shot failure. The copy and line height of the bottom-left section have changed.</figcaption>
</figure>

<p>These tests force us to proactively highlight intentional changes to UI (like updated colors, copy, or constraints), keep us informed of unintended changes, and build living documentation of an app’s UI—these artifacts can be checked into source control and highlight changes in GitHub pull requests. They provide far more coverage than a unit test normally allows—an assertion covers every pixel on the screen—but snapshot tests aren’t limited to screen shots.</p>

<p>Facebook maintains another popular testing library with snapshot support: <a href="http://facebook.github.io/jest/">Jest</a>. Like <code class="language-plaintext highlighter-rouge">FBSnapshotTestCase</code>, Jest snapshots ensure that UI doesn’t change unexpectedly, but where the former captures images, the latter captures text. Jest serializes and records data structures, particularly <a href="https://facebook.github.io/react/">React</a> view trees. This means that even non-visual UI elements, like accessibility attributes, can be tested. In fact, because Jest works with text, it can go beyond UI testing and write snapshots against <em>any</em> serializable data.</p>

<p>It turns out that snapshots are great for testing complex data structures with lots of properties! Snapshots also let us quickly generate coverage for untested code, unlocking the ability to refactor where refactoring seemed impossible.</p>

<h3 id="snapping-structures-in-swift">Snapping Structures in Swift</h3>

<p>While <code class="language-plaintext highlighter-rouge">FBSnapshotTestCase</code> has popularized the idea of screen shot tests in the iOS community, more generalized snapshot testing hasn’t yet taken hold. What might a snapshot test look like in our world? Let’s walk through a few examples.</p>

<p>It’s common to have an API service in a code base, and somewhere in that service there will be code that is responsible for preparing a <code class="language-plaintext highlighter-rouge">URLRequest</code> value out of given data. This preparation work constructs the <code class="language-plaintext highlighter-rouge">URL</code> for the request, attaches authentication, some headers, and perhaps sets the <code class="language-plaintext highlighter-rouge">POST</code> body. All of this data can be captured easily in a snapshot test!</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">testUrlRequestPreparation</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">service</span> <span class="o">=</span> <span class="kt">ApiService</span><span class="p">()</span>
  <span class="k">let</span> <span class="nv">request</span> <span class="o">=</span> <span class="n">service</span>
    <span class="o">.</span><span class="nf">prepare</span><span class="p">(</span><span class="nv">endpoint</span><span class="p">:</span> <span class="o">.</span><span class="nf">createArticle</span><span class="p">(</span><span class="s">"Hello, world!"</span><span class="p">))</span>

  <span class="nf">assertSnapshot</span><span class="p">(</span><span class="nv">matching</span><span class="p">:</span> <span class="n">request</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The resulting snapshot may look something like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>▿ https://api.site.com/articles?oauth_token=deadbeef
  ▿ url: Optional(https://api.site.com/articles?oauth_token=deadbeef)
    ▿ some: https://api.site.com/articles?oauth_token=deadbeef
      - _url: https://api.site.com/articles?oauth_token=deadbeef #0
        - super: NSObject
  - cachePolicy: 0
  - timeoutInterval: 60.0
  - mainDocumentURL: nil
  - networkServiceType: __ObjC.NSURLRequest.NetworkServiceType
  - allowsCellularAccess: true
  ▿ httpMethod: Optional("POST")
    - some: "POST"
  ▿ allHTTPHeaderFields: Optional(["App-Version": "42"])
    ▿ some: 1 key/value pairs
      ▿ (2 elements)
        - key: "App-Version"
        - value: "42"
  ▿ httpBody: Optional(19 bytes)
    ▿ some: "body=Hello%20world!"
  - httpBodyStream: nil
  - httpShouldHandleCookies: true
  - httpShouldUsePipelining: false
</code></pre></div></div>

<p>That’s a lot of surface area covered with very little effort!</p>

<p>An app may similarly communicate with an analytics service and buffer tracking events. You could use snapshot testing to capture all of these events and their properties.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">testFavorite</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">analytics</span> <span class="o">=</span> <span class="kt">Analytics</span><span class="p">()</span>
  <span class="n">analytics</span><span class="o">.</span><span class="nf">track</span><span class="p">(</span><span class="nv">event</span><span class="p">:</span> <span class="o">.</span><span class="nf">favorite</span><span class="p">(</span><span class="nv">articleId</span><span class="p">:</span> <span class="mi">1</span><span class="p">))</span>
  <span class="n">analytics</span><span class="o">.</span><span class="nf">track</span><span class="p">(</span><span class="nv">event</span><span class="p">:</span> <span class="o">.</span><span class="nf">unfavorite</span><span class="p">(</span><span class="nv">articleId</span><span class="p">:</span> <span class="mi">1</span><span class="p">))</span>
  
  <span class="nf">assertSnapshot</span><span class="p">(</span><span class="nv">matching</span><span class="p">:</span> <span class="n">analytics</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The resulting snapshot is a detailed view of the underlying data.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>▿ App.Analytics #0
  ▿ buffer: 2 elements
    ▿ 2 key/value pairs
      ▿ (2 elements)
        - key: "event"
        - value: "Favorite"
      ▿ (2 elements)
        - key: "properties"
        ▿ value: 30 key/value pairs
          ▿ (2 elements)
            - key: "article_id"
            - value: 1
          ▿ (2 elements)
            - key: "app_version"
            - value: 42
          ▿ (2 elements)
            - key: "ios_version"
            - value: "11.0"
          …
    ▿ 2 key/value pairs
      ▿ (2 elements)
        - key: "event"
        - value: "Unfavorite"
      ▿ (2 elements)
        - key: "properties"
        ▿ value: 30 key/value pairs
          ▿ (2 elements)
            - key: "article_id"
            - value: 1
          ▿ (2 elements)
            - key: "app_version"
            - value: 42
          ▿ (2 elements)
            - key: "ios_version"
            - value: "11.0"
          …            
</code></pre></div></div>

<p>With server-side Swift maturing, snapshots could cover an entire request/response lifecycle!</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">testBasicAuthWhenUnauthorized</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">app</span> <span class="o">=</span> <span class="nf">basicAuth</span><span class="p">(</span><span class="nv">user</span><span class="p">:</span> <span class="s">"admin"</span><span class="p">,</span> <span class="nv">password</span><span class="p">:</span> <span class="s">"secret"</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">writeStatus</span><span class="p">(</span><span class="o">.</span><span class="n">ok</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">respond</span><span class="p">(</span><span class="nv">html</span><span class="p">:</span> <span class="s">"&lt;p&gt;Private!&lt;/p&gt;"</span><span class="p">)</span>
  <span class="k">let</span> <span class="nv">request</span> <span class="o">=</span> <span class="kt">URLRequest</span><span class="p">(</span><span class="nv">url</span><span class="p">:</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="s">"/"</span><span class="p">)</span><span class="o">!</span><span class="p">)</span>

  <span class="nf">assertSnapshot</span><span class="p">(</span><span class="nv">matching</span><span class="p">:</span> <span class="n">app</span><span class="o">.</span><span class="nf">connect</span><span class="p">(</span><span class="n">request</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>

<p>A <code class="language-plaintext highlighter-rouge">CustomStringConvertible</code>-like protocol (<code class="language-plaintext highlighter-rouge">Snapshottable</code>?) might render a snapshot like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>▿ Request
  GET /

▿ Response
  HTTP/1.1 401 Unauthorized
  Content-Type: text/plain
  WWW-Authenticate: Basic

  Please authenticate.
</code></pre></div></div>

<p>Once you start writing your first snapshot tests you begin to realize how many places in your code have only partial coverage and could be improved.</p>

<p>How do we make these ideas realities? Let’s explore what goes into writing a snapshot test and build one from first principles.</p>

<h3 id="snapshots-from-scratch">Snapshots from Scratch</h3>

<p>Let’s pretend we’re writing an application that keeps its state in a centralized store.<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote">3</a></sup> For the moment, we have a structure representing our user and a container structure holding our app’s state, namely whether or not a user is logged in or available.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">User</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">name</span><span class="p">:</span> <span class="kt">String</span>
<span class="p">}</span>

<span class="kd">struct</span> <span class="kt">AppState</span> <span class="p">{</span>
  <span class="k">var</span> <span class="nv">user</span><span class="p">:</span> <span class="kt">User</span><span class="p">?</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We can start by writing a unit test against the initial state of our data structure. In this case, there should be no user attached by default.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">AppStateTests</span><span class="p">:</span> <span class="kt">XCTestCase</span> <span class="p">{</span>
  <span class="kd">func</span> <span class="nf">testInitialState</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">state</span> <span class="o">=</span> <span class="kt">AppState</span><span class="p">()</span>

    <span class="kt">XCTAssertNil</span><span class="p">(</span><span class="n">state</span><span class="o">.</span><span class="n">user</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This test is simple and explicit: it completely covers our expectations of state, and it passes! Over time, though, we add new properties to <code class="language-plaintext highlighter-rouge">AppState</code>, and this test <em>still</em> passes. Nothing reminds us to revisit it as associated code changes. Nothing encourages us to write additional assertions. A snapshot test, on the other hand, would capture the entire structure of our data and force us to reflect and return as our app model grows and changes: it would capture default state over time.</p>

<p>Let’s ditch our unit test and write the bookend of a snapshot test instead. We start by preparing our data, as we did in our unit test, but this time we’ll assert against a snapshot using a reference.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">AppStateTests</span><span class="p">:</span> <span class="kt">XCTestCase</span> <span class="p">{</span>
  <span class="kd">func</span> <span class="nf">testInitialState</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">state</span> <span class="o">=</span> <span class="kt">AppState</span><span class="p">()</span>

    <span class="c1">// ???</span>

    <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">reference</span><span class="p">,</span> <span class="n">snapshot</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The snapshot can be a string describing our data. The reference can be an empty string to satisfy the compiler so that we can run the test.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">testInitialState</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">state</span> <span class="o">=</span> <span class="kt">AppState</span><span class="p">()</span>

  <span class="k">let</span> <span class="nv">snapshot</span> <span class="o">=</span> <span class="kt">String</span><span class="p">(</span><span class="nv">describing</span><span class="p">:</span> <span class="n">state</span><span class="p">)</span>
  <span class="k">let</span> <span class="nv">reference</span> <span class="o">=</span> <span class="s">""</span>

  <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">reference</span><span class="p">,</span> <span class="n">snapshot</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We expect this test to fail, and it does!</p>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">XCTAssertEqual failed: ("") is not equal to ("AppState(user: nil)") -</code></p>
</blockquote>

<p>We can take advantage of this failure message, though, and plug it into our reference data.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">testInitialState</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">state</span> <span class="o">=</span> <span class="kt">AppState</span><span class="p">()</span>

  <span class="k">let</span> <span class="nv">snapshot</span> <span class="o">=</span> <span class="kt">String</span><span class="p">(</span><span class="nv">describing</span><span class="p">:</span> <span class="n">state</span><span class="p">)</span>
  <span class="k">let</span> <span class="nv">reference</span> <span class="o">=</span> <span class="s">"AppState(user: nil)"</span>

  <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">reference</span><span class="p">,</span> <span class="n">snapshot</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It passes! We just test-drove our first snapshot test! It works as-is, but there are a couple problems: the way we take a snapshot doesn’t produce the most human-readable thing in the world (large data structures will spread out further and further over a single, long line), and <code class="language-plaintext highlighter-rouge">String.init(describing:)</code> uses the <code class="language-plaintext highlighter-rouge">CustomStringConvertible</code> protocol, which can omit information about our data.</p>

<p>Swift provides another function, <a href="https://developer.apple.com/documentation/swift/1641218-dump"><code class="language-plaintext highlighter-rouge">dump</code></a>, which solves both of these problems: it takes any data and renders its raw contents over multiple lines. Converting our test to use <code class="language-plaintext highlighter-rouge">dump</code> is a succinct exercise involving mutability and an <a href="https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID545">in-out parameter</a>.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">testInitialState</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">state</span> <span class="o">=</span> <span class="kt">AppState</span><span class="p">()</span>

  <span class="k">var</span> <span class="nv">snapshot</span> <span class="o">=</span> <span class="s">""</span>
  <span class="nf">dump</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="nv">to</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">snapshot</span><span class="p">)</span>

  <span class="k">let</span> <span class="nv">reference</span> <span class="o">=</span> <span class="s">"AppState(user: nil)"</span>
  <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">reference</span><span class="p">,</span> <span class="n">snapshot</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now we can re-run the test and, on failure, copy over our new reference.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">testInitialState</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">state</span> <span class="o">=</span> <span class="kt">AppState</span><span class="p">()</span>

  <span class="k">var</span> <span class="nv">snapshot</span> <span class="o">=</span> <span class="s">""</span>
  <span class="nf">dump</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="nv">to</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">snapshot</span><span class="p">)</span>

  <span class="k">let</span> <span class="nv">reference</span> <span class="o">=</span> <span class="s">"""
▿ SnapshotsTests.AppState
  - user: nil
"""</span>
  <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">reference</span><span class="p">,</span> <span class="n">snapshot</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Manually writing and updating snapshots isn’t very efficient. <code class="language-plaintext highlighter-rouge">FBSnapshotTestCase</code> and Jest write their snapshots to disk <em>for</em> us, so let’s support that next.</p>

<p>We’ll need a place to store our snapshots. <code class="language-plaintext highlighter-rouge">FBSnapshotTestCase</code> suggests an Xcode build scheme configuration to determine where screen shots are saved, but Jest automatically saves snapshots adjacent to their test files, nested in a <code class="language-plaintext highlighter-rouge">__snapshots__</code> directory. We can follow Jest’s lead and avoid configuration by using Swift’s <code class="language-plaintext highlighter-rouge">#file</code> identifier to produce a directory name relative to our test file.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">let</span> <span class="nv">snapshotDirectoryUrl</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">fileURLWithPath</span><span class="p">:</span> <span class="s">"</span><span class="se">\(</span><span class="kd">#file</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">deletingPathExtension</span><span class="p">()</span>
</code></pre></div></div>

<p>We delete the <code class="language-plaintext highlighter-rouge">.swift</code> path extension and have a snapshot directory name at hand!</p>

<p>How do we differentiate each snapshot file? We can use Swift’s <code class="language-plaintext highlighter-rouge">#function</code> identifier to name the snapshot after the current test function.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">let</span> <span class="nv">snapshotFileUrl</span> <span class="o">=</span> <span class="n">snapshotDirectoryUrl</span>
    <span class="o">.</span><span class="nf">appendingPathComponent</span><span class="p">(</span><span class="kd">#function</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">appendingPathExtension</span><span class="p">(</span><span class="s">"txt"</span><span class="p">)</span>
</code></pre></div></div>

<p>We append <code class="language-plaintext highlighter-rouge">.txt</code> to make the snapshots easy to preview in Finder and wherever else extension determines file kind.</p>

<p>Now that we have our directory and file templates, let’s use them! We can always attempt to create the snapshot directory in the case it doesn’t already exist.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">let</span> <span class="nv">fileManager</span> <span class="o">=</span> <span class="kt">FileManager</span><span class="o">.</span><span class="k">default</span>
  <span class="k">try!</span> <span class="n">fileManager</span>
    <span class="o">.</span><span class="nf">createDirectory</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="n">snapshotDirectoryUrl</span><span class="p">,</span>
                     <span class="nv">withIntermediateDirectories</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
</code></pre></div></div>

<p>We still need to determine whether or not we have an existing snapshot. If we do, we can test that our current data matches. If we don’t, we can record a snapshot for next time.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">if</span> <span class="n">fileManager</span><span class="o">.</span><span class="nf">fileExists</span><span class="p">(</span><span class="nv">atPath</span><span class="p">:</span> <span class="n">snapshotFileUrl</span><span class="o">.</span><span class="n">path</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">reference</span> <span class="o">=</span>
      <span class="k">try!</span> <span class="kt">String</span><span class="p">(</span><span class="nv">contentsOf</span><span class="p">:</span> <span class="n">snapshotFileUrl</span><span class="p">,</span> <span class="nv">encoding</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span>
    <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">reference</span><span class="p">,</span> <span class="n">snapshot</span><span class="p">)</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">try!</span> <span class="n">snapshot</span>
      <span class="o">.</span><span class="nf">write</span><span class="p">(</span><span class="nv">to</span><span class="p">:</span> <span class="n">snapshotFileUrl</span><span class="p">,</span> <span class="nv">atomically</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nv">encoding</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span>
    <span class="kt">XCTFail</span><span class="p">(</span><span class="s">"Wrote snapshot:</span><span class="se">\n\n\(</span><span class="n">snapshot</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
  <span class="p">}</span>
</code></pre></div></div>

<p>Upon recording a snapshot, we fail the test to encourage us to confirm that the snapshot looks right.</p>

<p>Let’s take a look at our test in full.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">testInitialState</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">state</span> <span class="o">=</span> <span class="kt">AppState</span><span class="p">()</span>

  <span class="k">var</span> <span class="nv">snapshot</span> <span class="o">=</span> <span class="s">""</span>
  <span class="nf">dump</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="nv">to</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">snapshot</span><span class="p">)</span>

  <span class="k">let</span> <span class="nv">snapshotDirectoryUrl</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">fileURLWithPath</span><span class="p">:</span> <span class="s">"</span><span class="se">\(</span><span class="kd">#file</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">deletingPathExtension</span><span class="p">()</span>

  <span class="k">let</span> <span class="nv">snapshotFileUrl</span> <span class="o">=</span> <span class="n">snapshotDirectoryUrl</span>
    <span class="o">.</span><span class="nf">appendingPathComponent</span><span class="p">(</span><span class="kd">#function</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">appendingPathExtension</span><span class="p">(</span><span class="s">"txt"</span><span class="p">)</span>

  <span class="k">let</span> <span class="nv">fileManager</span> <span class="o">=</span> <span class="kt">FileManager</span><span class="o">.</span><span class="k">default</span>
  <span class="k">try!</span> <span class="n">fileManager</span>
    <span class="o">.</span><span class="nf">createDirectory</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="n">snapshotDirectoryUrl</span><span class="p">,</span>
                     <span class="nv">withIntermediateDirectories</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>

  <span class="k">if</span> <span class="n">fileManager</span><span class="o">.</span><span class="nf">fileExists</span><span class="p">(</span><span class="nv">atPath</span><span class="p">:</span> <span class="n">snapshotFileUrl</span><span class="o">.</span><span class="n">path</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">reference</span> <span class="o">=</span>
      <span class="k">try!</span> <span class="kt">String</span><span class="p">(</span><span class="nv">contentsOf</span><span class="p">:</span> <span class="n">snapshotFileUrl</span><span class="p">,</span> <span class="nv">encoding</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span>
    <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">reference</span><span class="p">,</span> <span class="n">snapshot</span><span class="p">)</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">try!</span> <span class="n">snapshot</span>
      <span class="o">.</span><span class="nf">write</span><span class="p">(</span><span class="nv">to</span><span class="p">:</span> <span class="n">snapshotFileUrl</span><span class="p">,</span> <span class="nv">atomically</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nv">encoding</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span>
    <span class="kt">XCTFail</span><span class="p">(</span><span class="s">"Wrote snapshot:</span><span class="se">\n\n\(</span><span class="n">snapshot</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>That’s not so bad, but it’s hardly scalable per snapshot. Let’s extract this logic to a function that’s reusable across tests.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// SnapshotTesting.swift</span>

<span class="kd">func</span> <span class="nf">assertSnapshot</span><span class="p">(</span><span class="n">matching</span> <span class="nv">any</span><span class="p">:</span> <span class="kt">Any</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// ???</span>
<span class="p">}</span>
</code></pre></div></div>

<p>How much of our logic do we need to change? Not much! We can start by replacing our <code class="language-plaintext highlighter-rouge">state</code> variable with our <code class="language-plaintext highlighter-rouge">any</code> input.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">assertSnapshot</span><span class="p">(</span><span class="n">matching</span> <span class="nv">any</span><span class="p">:</span> <span class="kt">Any</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">var</span> <span class="nv">snapshot</span> <span class="o">=</span> <span class="s">""</span>
  <span class="nf">dump</span><span class="p">(</span><span class="n">any</span><span class="p">,</span> <span class="nv">to</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">snapshot</span><span class="p">)</span>

  <span class="k">let</span> <span class="nv">snapshotDirectoryUrl</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">fileURLWithPath</span><span class="p">:</span> <span class="s">"</span><span class="se">\(</span><span class="kd">#file</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">deletingPathExtension</span><span class="p">()</span>
  <span class="k">let</span> <span class="nv">snapshotFileUrl</span> <span class="o">=</span> <span class="n">snapshotDirectoryUrl</span>
    <span class="o">.</span><span class="nf">appendingPathComponent</span><span class="p">(</span><span class="kd">#function</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">appendingPathExtension</span><span class="p">(</span><span class="s">"txt"</span><span class="p">)</span>

  <span class="k">let</span> <span class="nv">fileManager</span> <span class="o">=</span> <span class="kt">FileManager</span><span class="o">.</span><span class="k">default</span>
  <span class="k">try!</span> <span class="n">fileManager</span>
    <span class="o">.</span><span class="nf">createDirectory</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="n">snapshotDirectoryUrl</span><span class="p">,</span>
                     <span class="nv">withIntermediateDirectories</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>

  <span class="k">if</span> <span class="n">fileManager</span><span class="o">.</span><span class="nf">fileExists</span><span class="p">(</span><span class="nv">atPath</span><span class="p">:</span> <span class="n">snapshotFileUrl</span><span class="o">.</span><span class="n">path</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">reference</span> <span class="o">=</span>
      <span class="k">try!</span> <span class="kt">String</span><span class="p">(</span><span class="nv">contentsOf</span><span class="p">:</span> <span class="n">snapshotFileUrl</span><span class="p">,</span> <span class="nv">encoding</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span>
    <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">reference</span><span class="p">,</span> <span class="n">snapshot</span><span class="p">)</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">try!</span> <span class="n">snapshot</span>
      <span class="o">.</span><span class="nf">write</span><span class="p">(</span><span class="nv">to</span><span class="p">:</span> <span class="n">snapshotFileUrl</span><span class="p">,</span> <span class="nv">atomically</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nv">encoding</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span>
    <span class="kt">XCTFail</span><span class="p">(</span><span class="s">"Wrote snapshot:</span><span class="se">\n\n\(</span><span class="n">snapshot</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now our test case reduces to this:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">AppStateTests</span><span class="p">:</span> <span class="kt">XCTestCase</span> <span class="p">{</span>
  <span class="kd">func</span> <span class="nf">testInitialState</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">state</span> <span class="o">=</span> <span class="kt">AppState</span><span class="p">()</span>

    <span class="nf">assertSnapshot</span><span class="p">(</span><span class="nv">matching</span><span class="p">:</span> <span class="n">state</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>When we run our suite, the snapshot is recorded to disk, and the test passes on the next run, but we notice a problem. Xcode highlighted our failure <em>inside</em> our <code class="language-plaintext highlighter-rouge">assertSnapshot</code> function, specifically on the line that calls <code class="language-plaintext highlighter-rouge">XCTFail</code>. If we were to try to write a second snapshot test, we’d hit another issue: snapshots now write out relative to our <code class="language-plaintext highlighter-rouge">SnapshotTesting.swift</code> file and are always called <code class="language-plaintext highlighter-rouge">assertSnapshot(matching:).txt</code>, named after our new helper!</p>

<p>We can solve these problems by knowing a little more about Swift’s <code class="language-plaintext highlighter-rouge">#</code>-prefixed identifiers. When <code class="language-plaintext highlighter-rouge">#file</code> and <code class="language-plaintext highlighter-rouge">#line</code> are evaluated as default parameters to a function, they refer to the <em>calling</em> function’s file name and line number. (The <code class="language-plaintext highlighter-rouge">#function</code> identifier likewise refers to the caller’s function name.) <code class="language-plaintext highlighter-rouge">XCTest</code> assertions define default <code class="language-plaintext highlighter-rouge">file</code> and <code class="language-plaintext highlighter-rouge">line</code> parameters and Xcode uses them to highlight failures. If we use these defaults ourselves, we can pass them along so that Xcode highlights things appropriately, and so that our snapshot files are saved appropriately.</p>

<p>We’re left, finally, with a reusable function that manages snapshots for us and highlights regressions inline.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">assertSnapshot</span><span class="p">(</span>
  <span class="n">matching</span> <span class="nv">any</span><span class="p">:</span> <span class="kt">Any</span><span class="p">,</span>
  <span class="nv">file</span><span class="p">:</span> <span class="kt">StaticString</span> <span class="o">=</span> <span class="kd">#file</span><span class="p">,</span>
  <span class="nv">function</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="kd">#function</span><span class="p">,</span>
  <span class="nv">line</span><span class="p">:</span> <span class="kt">UInt</span> <span class="o">=</span> <span class="kd">#line</span><span class="p">)</span>
<span class="p">{</span>
  <span class="k">var</span> <span class="nv">snapshot</span> <span class="o">=</span> <span class="s">""</span>
  <span class="nf">dump</span><span class="p">(</span><span class="n">any</span><span class="p">,</span> <span class="nv">to</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">snapshot</span><span class="p">)</span>

  <span class="k">let</span> <span class="nv">snapshotDirectoryUrl</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">fileURLWithPath</span><span class="p">:</span> <span class="s">"</span><span class="se">\(</span><span class="n">file</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">deletingPathExtension</span><span class="p">()</span>
  <span class="k">let</span> <span class="nv">snapshotFileUrl</span> <span class="o">=</span> <span class="n">snapshotDirectoryUrl</span>
    <span class="o">.</span><span class="nf">appendingPathComponent</span><span class="p">(</span><span class="n">function</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">appendingPathExtension</span><span class="p">(</span><span class="s">"txt"</span><span class="p">)</span>

  <span class="k">let</span> <span class="nv">fileManager</span> <span class="o">=</span> <span class="kt">FileManager</span><span class="o">.</span><span class="k">default</span>
  <span class="k">try!</span> <span class="n">fileManager</span>
    <span class="o">.</span><span class="nf">createDirectory</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="n">snapshotDirectoryUrl</span><span class="p">,</span>
                     <span class="nv">withIntermediateDirectories</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>

  <span class="k">if</span> <span class="n">fileManager</span><span class="o">.</span><span class="nf">fileExists</span><span class="p">(</span><span class="nv">atPath</span><span class="p">:</span> <span class="n">snapshotFileUrl</span><span class="o">.</span><span class="n">path</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">reference</span> <span class="o">=</span>
      <span class="k">try!</span> <span class="kt">String</span><span class="p">(</span><span class="nv">contentsOf</span><span class="p">:</span> <span class="n">snapshotFileUrl</span><span class="p">,</span> <span class="nv">encoding</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span>
    <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">reference</span><span class="p">,</span> <span class="n">snapshot</span><span class="p">,</span> <span class="nv">file</span><span class="p">:</span> <span class="n">file</span><span class="p">,</span> <span class="nv">line</span><span class="p">:</span> <span class="n">line</span><span class="p">)</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">try!</span> <span class="n">snapshot</span>
      <span class="o">.</span><span class="nf">write</span><span class="p">(</span><span class="nv">to</span><span class="p">:</span> <span class="n">snapshotFileUrl</span><span class="p">,</span> <span class="nv">atomically</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nv">encoding</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span>
    <span class="kt">XCTFail</span><span class="p">(</span><span class="s">"Wrote snapshot:</span><span class="se">\n\n\(</span><span class="n">snapshot</span><span class="se">)</span><span class="s">"</span><span class="p">,</span>
            <span class="nv">file</span><span class="p">:</span> <span class="n">file</span><span class="p">,</span>
            <span class="nv">line</span><span class="p">:</span> <span class="n">line</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Let’s take one last before/after look at our test.</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code> final class AppStateTests: XCTestCase {
   func testInitialState() {
     let state = AppState()
 
<span class="gd">-    XCTAssertNil(state.user)
</span><span class="gi">+    assertSnapshot(matching: state)
</span>   }
 }
</code></pre></div></div>

<p>The snapshot version will provide much better regression test coverage as <code class="language-plaintext highlighter-rouge">AppState</code> grows and changes, but we can begin to see some of its shortcomings. The original test documents our expectation in-line, while the snapshot test files our expectation away and out of sight. Granular business logic is probably best tested explicitly, while snapshots can play a supporting role.</p>

<p>We should consider other shortcomings of our approach and see if we can address them. Recording snapshots is automatic and easy, but rerecording them requires a trip to the file system. Snapshot tests are brittle: we expect them to fail when associated code changes, so updating them should be just as easy as recording them.</p>

<p>Our failure output could also be better. We highlight when a snapshot doesn’t match its reference by printing the entire reference and snapshot, but we make no effort to zoom in on the difference, which could be difficult to spot. Usability improvements to these problems (and others) are suggested as exercises at the end of this article.</p>

<p>Let’s wrap up with a final note on tooling, because Xcode could solve some of these problems even better! Imagine a world where our environment offered first-class support for snapshots, providing a way to see them alongside their tests and an interface for viewing and updating them when they change. Xcode could even let the community fill this gap by providing more extensibility in future releases. The <a href="https://developer.apple.com/documentation/xctest/activities_and_attachments">activities and attachments</a> APIs help, but they fall a bit short: attachments are hidden away in the report navigator and live locally with build products, so they can’t easily be checked into source control. Xcode improvements will always be dreams before realities, but in the meantime we can <a href="https://bugreport.apple.com">file a radar</a> to share them.</p>

<h3 id="conclusion">Conclusion</h3>

<p>We’ve covered what snapshot testing is, what it isn’t, and even implemented our own tool for the job! It’s one of many testing aids to keep in mind when writing durable, regression-proof software, and it can make our lives much easier while we write and refactor our apps.</p>

<p>An expanded version of the code above is available as a library, <a href="https://github.com/pointfreeco/swift-snapshot-testing">SnapshotTesting</a>, and contains many improvements, including those suggested as exercises below.</p>

<!-- TODO: Hire/follow me callout -->

<h3 id="exercises">Exercises</h3>

<p>If you’ve had fun following along, there are plenty of enhancements that can be made! Here are a few ideas to get you started:</p>

<ul>
  <li>
    <p>Add a <code class="language-plaintext highlighter-rouge">record</code> argument to our snapshot assertion function. When enabled, a snapshot should be written even if a reference already exists.</p>
  </li>
  <li>
    <p>The following snapshot test will fail on repeat runs.</p>

    <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">assertSnapshot</span><span class="p">(</span><span class="nv">matching</span><span class="p">:</span> <span class="kt">NSObject</span><span class="p">())</span>
<span class="c1">// XCTAssertEqual failed: ("- &lt;NSObject: 0x1023006d0&gt; #0</span>
<span class="c1">// ") is not equal to ("- &lt;NSObject: 0x10245b840&gt; #0</span>
<span class="c1">// ") -</span>
</code></pre></div>    </div>

    <p><code class="language-plaintext highlighter-rouge">NSObject</code> dumps its memory address, which differs with every object. How can we accommodate this variance in our assertion? What if the object is nested deeply in our data structure?</p>
  </li>
  <li>
    <p>Add support for custom snapshot output: <code class="language-plaintext highlighter-rouge">dump</code> is a nice default, but some data may be best recorded and read in another format.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">assertSnapshot</code> can only associate a single snapshot per test. Modify it so that a single test can make more than one snapshot assertion.</p>
  </li>
  <li>
    <p>Our snapshot tests use <code class="language-plaintext highlighter-rouge">XCTAssertEqual</code> and failure messages render our complete reference and snapshot all at once. Use a line diff algorithm to highlight differences more clearly. A popular version is described in Myers’ 1986 paper, “<a href="http://www.xmailserver.org/diff2.pdf">An O(ND) Difference Algorithm and its Variations</a>.”</p>
  </li>
  <li>
    <p>Generalize snapshots beyond text. How can we use the same system to test against images and values that produce them?</p>

    <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">assertSnapshot</span><span class="p">(</span><span class="nv">matching</span><span class="p">:</span> <span class="n">myUIView</span><span class="p">)</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Write code that, during a test run, determines if any snapshots are “stale” (<em>i.e.</em>, not asserted against for the duration of the suite) and prints them to output.</p>
  </li>
</ul>

<hr />
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Snapshot tests are sometimes called “<a href="https://en.wikipedia.org/wiki/Characterization_test">characterization tests</a>.”</p>

      <p>Libraries like <a href="https://github.com/venmo/DVR">DVR</a>, which records network responses, are not snapshot testing libraries <em>per se</em>, but they <em>are</em> related: they cache expensive, IO-bound computations to prevent slow, sporadically-failing test runs. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>You’ll also need to make sure your test class inherits from <code class="language-plaintext highlighter-rouge">FBSnapshotTestCase</code> and that its <code class="language-plaintext highlighter-rouge">recordMode</code> is set to <code class="language-plaintext highlighter-rouge">true</code>. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>It’s a popular choice these days in front-end web development (see <a href="http://elm-lang.org">Elm</a> and <a href="http://redux.js.org">Redux</a>), and is seeing some adoption in our world. Chris Eidhof demonstrates the pattern in “<a href="http://chris.eidhof.nl/post/reducers/">Reducers</a>” (be sure to watch <a href="https://talk.objc.io/episodes/S01E62-testable-view-controllers-with-reducers">the Swift Talk episode</a>). <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content>
  </entry>
  
</feed>
