Convert Figma logo to code with AI

pointfreeco logoswift-snapshot-testing

📸 Delightful Swift snapshot testing.

3,743
570
3,743
182

Top Related Projects

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.

Snapshot view unit tests for iOS

Quick Overview

The swift-snapshot-testing library is a powerful tool for writing snapshot tests in Swift projects. It provides a simple and intuitive API for capturing and comparing the visual representation of your application's UI, allowing you to catch regressions and ensure consistent behavior across different versions of your app.

Pros

  • Ease of Use: The library offers a straightforward and intuitive API, making it easy to integrate snapshot testing into your existing test suite.
  • Flexibility: Supports a wide range of UI components, including views, view controllers, and even network responses.
  • Powerful Diffing: The built-in diffing capabilities make it easy to identify and understand changes between snapshots, facilitating the debugging process.
  • Cross-Platform Support: Works seamlessly with both iOS and macOS projects, allowing you to maintain consistency across your entire application ecosystem.

Cons

  • Potential for Flakiness: Snapshot tests can be sensitive to small changes in the UI, which may lead to false positives and require careful maintenance of the snapshot files.
  • Increased Test Suite Size: The snapshot files can quickly accumulate, leading to a larger test suite and potentially slower test execution times.
  • Dependency on External Tools: The library relies on external tools like xcresult and ImageDiff for certain functionality, which may require additional setup and configuration.
  • Limited Customization: While the library provides a good set of default options, the ability to customize the snapshot comparison process may be limited in some cases.

Code Examples

Capturing a Snapshot

import SnapshotTesting

let view = MyViewController()
assertSnapshot(matching: view, as: .image)

This code captures a snapshot of the MyViewController instance and compares it to the reference snapshot.

Customizing Snapshot Comparison

import SnapshotTesting

let view = MyViewController()
assertSnapshot(matching: view, as: .image(precision: 0.9))

This example customizes the snapshot comparison by adjusting the precision threshold, which can be useful when dealing with minor UI changes.

Verifying Network Responses

import SnapshotTesting

let response = try await URLSession.shared.data(from: url)
assertSnapshot(matching: response.data, as: .json)

This code captures a snapshot of the JSON data returned from a network request and compares it to the reference snapshot.

Getting Started

To get started with swift-snapshot-testing, follow these steps:

  1. Add the library to your project using a package manager like Swift Package Manager or CocoaPods.
dependencies: [
    .package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.10.0")
]
  1. Import the SnapshotTesting module in your test files.
import SnapshotTesting
  1. Configure the snapshot testing environment by setting up the appropriate snapshot strategies for your UI components.
// Set up snapshot strategies for different UI components
let strategies: [SnapshotStrategy] = [
    .image(precision: 0.99),
    .json(),
    .xml()
]
  1. Use the assertSnapshot function to capture and compare snapshots in your test cases.
func testMyViewController() {
    let view = MyViewController()
    assertSnapshot(matching: view, as: .image(on: .iPhone11))
}

That's the basic setup to get started with swift-snapshot-testing. Refer to the project's documentation for more advanced usage and configuration options.

Competitor Comparisons

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.

Pros of swift-composable-architecture

  • Provides a structured and opinionated approach to building scalable and maintainable iOS applications.
  • Encourages a clear separation of concerns, making it easier to reason about and test different parts of the application.
  • Supports a unidirectional data flow, which can lead to more predictable and reliable application behavior.

Cons of swift-composable-architecture

  • The learning curve can be steeper compared to more traditional iOS architectures, especially for developers new to functional programming concepts.
  • The boilerplate code required to set up the architecture can be more extensive than other approaches.
  • The library may not be as widely adopted as some other iOS architectures, which could make it harder to find resources and community support.

Code Comparison

swift-snapshot-testing

let sut = MyView()
let snapshot = try? sut.makeSnapshot()
expect(snapshot).to(matchSnapshot())

swift-composable-architecture

let store = TestStore(
  initialState: MyState(),
  reducer: myReducer,
  environment: MyEnvironment()
)

store.assert(
  .send(.myAction),
  .receive(.myResult) { $0 = $1 },
  .finish(wearing: .any)
)

Snapshot view unit tests for iOS

Pros of ios-snapshot-test-case

  • Provides a comprehensive set of utilities for taking and comparing UI snapshots, including support for different device configurations and orientations.
  • Integrates well with the Xcode testing framework, making it easy to incorporate into existing test suites.
  • Supports a wide range of UI components, including views, view controllers, and even custom UI elements.

Cons of ios-snapshot-test-case

  • Requires the use of Objective-C, which may not be preferred by developers working in a Swift-only codebase.
  • Lacks some of the more advanced features and customization options available in Swift-based snapshot testing libraries like Swift Snapshot Testing.
  • May have a steeper learning curve for developers unfamiliar with the Objective-C testing ecosystem.

Code Comparison

Swift Snapshot Testing:

import SnapshotTesting

let view = MyCustomView()
assertSnapshot(matching: view, as: .image)

ios-snapshot-test-case:

#import <FBSnapshotTestCase/FBSnapshotTestCase.h>

- (void)testMyCustomView {
    MyCustomView *view = [[MyCustomView alloc] init];
    FBSnapshotVerifyView(view, nil);
}

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

📸 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.