swift snapshot testing

📸 Delightful Swift snapshot testing.

3799
582
Swift

📸 SnapshotTesting

CI
Slack

Delightful Swift snapshot testing.

Usage

Once installed, no additional configuration is required. You can import the
SnapshotTesting module and call the assertSnapshot function.

import SnapshotTesting
import XCTest

class MyViewControllerTests: XCTestCase {
  func testMyViewController() {
    let vc = MyViewController()

    assertSnapshot(of: vc, as: .image)
  }
}

When an assertion first runs, a snapshot is automatically recorded to disk and the test will fail,
printing out the file path of any newly-recorded reference.

❌ failed - No reference was found on disk. Automatically recorded snapshot: …

open “…/MyAppTests/__Snapshots__/MyViewControllerTests/testMyViewController.png”

Re-run “testMyViewController” to test against the newly-recorded snapshot.

Repeat test runs will load this reference and compare it with the runtime value. If they don’t
match, the test will fail and describe the difference. Failures can be inspected from Xcode’s Report
Navigator or by inspecting the file URLs of the failure.

You can record a new reference by customizing snapshots inline with the assertion, or using the
withSnapshotTesting tool:

// Record just this one snapshot
assertSnapshot(of: vc, as: .image, record: .all)

// Record all snapshots in a scope:
withSnapshotTesting(record: .all) {
  assertSnapshot(of: vc1, as: .image)
  assertSnapshot(of: vc2, as: .image)
  assertSnapshot(of: vc3, as: .image)
}

// Record all snapshots in an XCTestCase subclass:
class FeatureTests: XCTestCase {
  override func invokeTest() {
    withSnapshotTesting(record: .all) {
      super.invokeTest()
    }
  }
}

Snapshot Anything

While most snapshot testing libraries in the Swift community are limited to UIImages of UIViews,
SnapshotTesting can work with any format of any value on any Swift platform!

The assertSnapshot function accepts a value and any snapshot strategy that value supports. This
means that a view or view controller can be tested against an image representation and against a
textual representation of its properties and subview hierarchy.

assertSnapshot(of: vc, as: .image)
assertSnapshot(of: vc, as: .recursiveDescription)

View testing is highly configurable. You can override trait collections (for specific size classes
and content size categories) and generate device-agnostic snapshots, all from a single simulator.

assertSnapshot(of: vc, as: .image(on: .iPhoneSe))
assertSnapshot(of: vc, as: .recursiveDescription(on: .iPhoneSe))

assertSnapshot(of: vc, as: .image(on: .iPhoneSe(.landscape)))
assertSnapshot(of: vc, as: .recursiveDescription(on: .iPhoneSe(.landscape)))

assertSnapshot(of: vc, as: .image(on: .iPhoneX))
assertSnapshot(of: vc, as: .recursiveDescription(on: .iPhoneX))

assertSnapshot(of: vc, as: .image(on: .iPadMini(.portrait)))
assertSnapshot(of: vc, as: .recursiveDescription(on: .iPadMini(.portrait)))

Warning
Snapshots must be compared using the exact same simulator that originally took the reference to
avoid discrepancies between images.

Better yet, SnapshotTesting isn’t limited to views and view controllers! There are a number of
available snapshot strategies to choose from.

For example, you can snapshot test URL requests (e.g., those that your API client prepares).

assertSnapshot(of: urlRequest, as: .raw)
// POST http://localhost:8080/account
// Cookie: pf_session={"userId":"1"}
//
// email=blob%40pointfree.co&name=Blob

And you can snapshot test Encodable values against their JSON and property list representations.

assertSnapshot(of: user, as: .json)
// {
//   "bio" : "Blobbed around the world.",
//   "id" : 1,
//   "name" : "Blobby"
// }

assertSnapshot(of: user, as: .plist)
// <?xml version="1.0" encoding="UTF-8"?>
// <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
//  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
// <plist version="1.0">
// <dict>
//   <key>bio</key>
//   <string>Blobbed around the world.</string>
//   <key>id</key>
//   <integer>1</integer>
//   <key>name</key>
//   <string>Blobby</string>
// </dict>
// </plist>

In fact, any value can be snapshot-tested by default using its
mirror!

assertSnapshot(of: user, as: .dump)
// â–ż User
//   - bio: "Blobbed around the world."
//   - id: 1
//   - name: "Blobby"

If your data can be represented as an image, text, or data, you can write a snapshot test for it!

Documentation

The latest documentation is available
here.

Installation

Xcode

Warning
By default, Xcode will try to add the SnapshotTesting package to your project’s main
application/framework target. Please ensure that SnapshotTesting is added to a test target
instead, as documented in the last step, below.

  1. From the File menu, navigate through Swift Packages and select
    Add Package Dependency….
  2. Enter package repository URL: https://github.com/pointfreeco/swift-snapshot-testing.
  3. Confirm the version and let Xcode resolve the package.
  4. On the final dialog, update SnapshotTesting’s Add to Target column to a test target that
    will contain snapshot tests (if you have more than one test target, you can later add
    SnapshotTesting to them by manually linking the library in its build phase).

Swift Package Manager

If you want to use SnapshotTesting in any other project that uses
SwiftPM, add the package as a dependency in Package.swift:

dependencies: [
  .package(
    url: "https://github.com/pointfreeco/swift-snapshot-testing",
    from: "1.12.0"
  ),
]

Next, add SnapshotTesting as a dependency of your test target:

targets: [
  .target(name: "MyApp"),
  .testTarget(
    name: "MyAppTests",
    dependencies: [
      "MyApp",
      .product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
    ]
  )
]

Features

  • Dozens of snapshot strategies. Snapshot
    testing isn’t just for UIViews and CALayers. Write snapshots against any value.
  • Write your own snapshot strategies.
    If you can convert it to an image, string, data, or your own diffable format, you can snapshot
    test it! Build your own snapshot strategies from scratch or transform existing ones.
  • No configuration required. Don’t fuss with scheme settings and environment variables.
    Snapshots are automatically saved alongside your tests.
  • More hands-off. New snapshots are recorded whether isRecording mode is true or not.
  • Subclass-free. Assert from any XCTest case or Quick spec.
  • Device-agnostic snapshots. Render views and view controllers for specific devices and trait
    collections from a single simulator.
  • First-class Xcode support. Image differences are captured as XCTest attachments. Text
    differences are rendered in inline error messages.
  • Supports any platform that supports Swift. Write snapshot tests for iOS, Linux, macOS, and
    tvOS.
  • SceneKit, SpriteKit, and WebKit support. Most snapshot testing libraries don’t support these
    view subclasses.
  • Codable support. Snapshot encodable data structures into their JSON and property list
    representations.
  • Custom diff tool integration. Configure failure messages to print diff commands for
    Kaleidoscope or your diff tool of choice.
    SnapshotTesting.diffToolCommand = { "ksdiff \($0) \($1)" }
    

Plug-ins

Have you written your own SnapshotTesting plug-in?
Add it here and
submit a pull request!

Related Tools

  • iOSSnapshotTestCase helped introduce screen
    shot testing to a broad audience in the iOS community. Experience with it inspired the creation
    of this library.

  • Jest brought generalized snapshot testing to the JavaScript community with
    a polished user experience. Several features of this library (diffing, automatically capturing
    new snapshots) were directly influenced.

Learn More

SnapshotTesting was designed with witness-oriented programming.

This concept (and more) are explored thoroughly in a series of episodes on
Point-Free, a video series exploring functional programming and Swift
hosted by Brandon Williams and
Stephen Celis.

Witness-oriented programming and the design of this library was explored in the following
Point-Free episodes:

video poster image

License

This library is released under the MIT license. See LICENSE for details.