Snapshot testing tool for iOS and tvOS

Overview

master

SnapshotTest is a simple view testing tool written completely in Swift to aid with development for Apple platforms. It's like unit testing for views.

How

When record mode is active a snapshot assertion will record an image of the view and save it to a specified directory. This will cause the test to fail. When record mode is deactivated the snapshot assertion will record an image of the view and compare it to the saved reference image. Should they differ the test will fail.

Setup

All SnapshotTest needs to know is where to save the reference images. This directory is specified using a test scheme environmental variable using the key:

REFERENCE_IMAGE_DIR

Recommended reference image directory path is:

$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages

It should look something like this:

Test scheme arguments.

Usage

If you are familiar with XCTest using SnapshotTest will be a breeze. Instead of subclassing XCTestCase you just need to subclass SnapshotTestCase and assert the view using AssertSnapshot() to test it.

class ViewTests: SnapshotTestCase {
    
    func testView_withAlteration() {
        // Given
        let view = View(frame: CGRect(x: 0, y: 0, width: 375, height: 100))
        
        // When
        view.alter()
        
        // Then
        AssertSnapshot(view)
    }
    
}

Currently UIView, UIViewController and CALayer are supported.

Record mode

Record mode records a snapshot of your view within the current scope and stores it in the reference directory to compare with any subsequent assertions.

Note that record mode will fail the test and output the reference image path to the console.

🔴 RECORD MODE: Reference image saved to /Users/snap/App/AppTests/ReferenceImages/View/testView_withAlteration.png

Test case

To set the test case to record mode simply change the recordMode property to true.

class ViewTests: SnapshotTestCase {
    
    override func setUp() {
        super.setUp()
        recordMode = true
    }
    
}

The assertion will then record and save a reference image.

Global

To set record mode globally and record all snapshots for every assertion, set the class variable recordMode instead.

SnapshotTestCase.recordMode = true

Note that you will probably need to set up a principal class to guarantee that record mode is activated before your test suite is run.

Statement

To explicitly record a single snapshot you can instead use the RecordSnapshot() function:

class ViewTests: SnapshotTestCase {
    
    func testView_withAlteration() {
        // Given
        let view = View(frame: CGRect(x: 0, y: 0, width: 375, height: 100))
        
        // When
        view.alter()
        
        // Then
        RecordSnapshot(view)
    }
    
}

Principal class

A principal class is automatically instantiated by XCTest when your test bundle is loaded. Think of it as a good place to put a global setUp() and tearDown(). A simple principal class might look like this:

import SnapshotTest

class TestObserver : NSObject {

    override init() {
        SnapshotTestCase.recordMode = true
    }
}

In your test bundle's Info.plist, add a key value pair:

<key>NSPrincipalClass</key>
<string>YourAppTests.TestObserver</string>

Where YourAppTests is the name of your test bundle.

This will activate record mode globally and is guaranteed to be run before your test suite.

Options

SnapshotTest provides different ways to compare snapshots using several options.

Option Description
device Compares snapshots specific to a certain device.
osVersion Compares snapshots specific to a certain OS version.

To use one or several options you just pass them to the options argument of the assertion:

AssertSnapshot(view, options: [.device, .osVersion])

Platforms

The following platforms and minimum versions are supported:

  • iOS 8.0
  • tvOS 9.0

Distribution

Use SnapshotTest by building it and integrating it into your project manually or by using a depencency manager. Currently only CocoaPods is supported with more to come.

CocoaPods

Just add the following line to your Podfile in the scope of your test target:

target "MyAppTests" do
  use_frameworks!
  pod 'SnapshotTest' ~> 'X.Y.Z'
end

Replace X.Y.Z with the starting version you wish to use. Breaking backward compatability will be a last resort kind of deal but specifying version is still recommended.

Contribute

SnapshotTest is licensed under the BSD 2-clause License and contributions are very welcome in the form of pull requests and issues.

Comments
  • Autolayout support

    Autolayout support

    Hello! I'd love to adopt SnapshotTest for a set of views, however they are sized based on their content and constraints. Is there a recommended setup so that they'll properly resolve their constraints and size themselves?

    Cheers!

    opened by yoiang 5
  • Multiple snapshots in one snapshot case

    Multiple snapshots in one snapshot case

    When recording in the following test file:

    @testable import Lovisa
    import SnapshotTest
    
    class PurchaseButtonsViewTests: SnapshotTestCase {
        
        private var sut: PurchaseButtonsView!
        
        override func setUp() {
            super.setUp()
            sut = PurchaseButtonsView(frame: CGRect(x: 0, y: 0, width: 375, height: 40))
            sut.viewClass = UIViewMock.self
        }
        
        override func tearDown() {
            sut = nil
            super.tearDown()
        }
        
        func testPurchaseButtonsViewWithPurchaseStatePurchase() {
            sut.purchaseState = .purchase
            AssertSnapshot(sut)
        }
    
        func testPurchaseButtonsViewWithPurchaseStateConfirmation() {
            sut.purchaseState = .confirmation
            AssertSnapshot(sut)
        }
        
        func testPurchaseButtonsViewWithPurchaseStatePurchased() {
            sut.purchaseState = .purchased
            AssertSnapshot(sut)
        }
        
    }
    
    

    Expected result:

    testPurchaseButtonsViewWithPurchaseStatePurchase
    
    testpurchasebuttonsviewwithpurchasestatepurchase 2x
    testPurchaseButtonsViewWithPurchaseStateConfirmation
    
    testpurchasebuttonsviewwithpurchasestateconfirmation 2x
    testPurchaseButtonsViewWithPurchaseStatePurchased
    
    testpurchasebuttonsviewwithpurchasestatepurchased 2x

    Actual result:

    testPurchaseButtonsViewWithPurchaseStatePurchase
    

    purchasebuttonsviewtests_testpurchasebuttonsviewwithpurchasestatepurchase

    testPurchaseButtonsViewWithPurchaseStateConfirmation
    

    purchasebuttonsviewtests_testpurchasebuttonsviewwithpurchasestateconfirmation

    testPurchaseButtonsViewWithPurchaseStatePurchased
    

    purchasebuttonsviewtests_testpurchasebuttonsviewwithpurchasestatepurchased

    Maybe has something to do with CGGraphicsContext not ending properly?

    bug blocker 
    opened by parski 1
  • Improved syntax for assertion/verification of snapshot

    Improved syntax for assertion/verification of snapshot

    The current syntax is very C macro. Verify(view: view) is the ugly cousin of XCTAssert(assertion) and not the least bit Swifty. It's back to the drawing board with this one.

    new feature 
    opened by parski 1
  • Print reference image path on record

    Print reference image path on record

    Fixes https://github.com/parski/SnapshotTest/issues/18.

    Outputs record mode failures like so:

    Test Case '-[CocoaPodsTests.CocoaPodsTests testLayerSnapshot]' started.
    /Users/parski/Development/SnapshotTest/Integration/CocoaPods/CocoaPodsTests/CocoaPodsTests.swift:68: error: -[CocoaPodsTests.CocoaPodsTests testLayerSnapshot] : failed - 🔴 RECORD MODE: Reference image saved to /Users/parski/Development/SnapshotTest/Integration/CocoaPods/CocoaPodsTests/ReferenceImages/CocoaPodsTests/testLayerSnapshot.png
    Test Case '-[CocoaPodsTests.CocoaPodsTests testLayerSnapshot]' failed (0.065 seconds).
    
    opened by parski 0
  • Bytes mismatch mystery

    Bytes mismatch mystery

    The normalized data of the snapshot is 26087 bytes in memory and the normalized data of the reference image is 26206 bytes in memory. They have identical graphical representations of their bitmaps, i.e. in Kaleidoscope they are identical.

    opened by parski 0
  • Bump tzinfo from 1.2.5 to 1.2.10

    Bump tzinfo from 1.2.5 to 1.2.10

    Bumps tzinfo from 1.2.5 to 1.2.10.

    Release notes

    Sourced from tzinfo's releases.

    v1.2.10

    TZInfo v1.2.10 on RubyGems.org

    v1.2.9

    • Fixed an incorrect InvalidTimezoneIdentifier exception raised when loading a zoneinfo file that includes rules specifying an additional transition to the final defined offset (for example, Africa/Casablanca in version 2018e of the Time Zone Database). #123.

    TZInfo v1.2.9 on RubyGems.org

    v1.2.8

    • Added support for handling "slim" format zoneinfo files that are produced by default by zic version 2020b and later. The POSIX-style TZ string is now used calculate DST transition times after the final defined transition in the file. The 64-bit section is now always used regardless of whether Time has support for 64-bit times. #120.
    • Rubinius is no longer supported.

    TZInfo v1.2.8 on RubyGems.org

    v1.2.7

    • Fixed 'wrong number of arguments' errors when running on JRuby 9.0. #114.
    • Fixed warnings when running on Ruby 2.8. #112.

    TZInfo v1.2.7 on RubyGems.org

    v1.2.6

    • Timezone#strftime('%s', time) will now return the correct number of seconds since the epoch. #91.
    • Removed the unused TZInfo::RubyDataSource::REQUIRE_PATH constant.
    • Fixed "SecurityError: Insecure operation - require" exceptions when loading data with recent Ruby releases in safe mode.
    • Fixed warnings when running on Ruby 2.7. #106 and #111.

    TZInfo v1.2.6 on RubyGems.org

    Changelog

    Sourced from tzinfo's changelog.

    Version 1.2.10 - 19-Jul-2022

    Version 1.2.9 - 16-Dec-2020

    • Fixed an incorrect InvalidTimezoneIdentifier exception raised when loading a zoneinfo file that includes rules specifying an additional transition to the final defined offset (for example, Africa/Casablanca in version 2018e of the Time Zone Database). #123.

    Version 1.2.8 - 8-Nov-2020

    • Added support for handling "slim" format zoneinfo files that are produced by default by zic version 2020b and later. The POSIX-style TZ string is now used calculate DST transition times after the final defined transition in the file. The 64-bit section is now always used regardless of whether Time has support for 64-bit times. #120.
    • Rubinius is no longer supported.

    Version 1.2.7 - 2-Apr-2020

    • Fixed 'wrong number of arguments' errors when running on JRuby 9.0. #114.
    • Fixed warnings when running on Ruby 2.8. #112.

    Version 1.2.6 - 24-Dec-2019

    • Timezone#strftime('%s', time) will now return the correct number of seconds since the epoch. #91.
    • Removed the unused TZInfo::RubyDataSource::REQUIRE_PATH constant.
    • Fixed "SecurityError: Insecure operation - require" exceptions when loading data with recent Ruby releases in safe mode.
    • Fixed warnings when running on Ruby 2.7. #106 and #111.
    Commits
    • 0814dcd Fix the release date.
    • fd05e2a Preparing v1.2.10.
    • b98c32e Merge branch 'fix-directory-traversal-1.2' into 1.2
    • ac3ee68 Remove unnecessary escaping of + within regex character classes.
    • 9d49bf9 Fix relative path loading tests.
    • 394c381 Remove private_constant for consistency and compatibility.
    • 5e9f990 Exclude Arch Linux's SECURITY file from the time zone index.
    • 17fc9e1 Workaround for 'Permission denied - NUL' errors with JRuby on Windows.
    • 6bd7a51 Update copyright years.
    • 9905ca9 Fix directory traversal in Timezone.get when using Ruby data source
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • Bump cocoapods-downloader from 1.2.2 to 1.6.3

    Bump cocoapods-downloader from 1.2.2 to 1.6.3

    Bumps cocoapods-downloader from 1.2.2 to 1.6.3.

    Release notes

    Sourced from cocoapods-downloader's releases.

    1.6.3

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.2

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.1

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.0

    Enhancements
    • None.
    Bug Fixes
    • Adds a check for command injections in the input for hg and git.
      orta #124

    1.5.1

    Enhancements
    • None.
    Bug Fixes
    • Fix "can't modify frozen string" errors when pods are integrated using the branch option
      buju77 #10920

    1.5.0

    ... (truncated)

    Changelog

    Sourced from cocoapods-downloader's changelog.

    1.6.3 (2022-04-01)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.2 (2022-03-28)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.1 (2022-03-23)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.0 (2022-03-22)

    Enhancements
    • None.
    Bug Fixes
    • Adds a check for command injections in the input for hg and git.
      orta #124

    1.5.1 (2021-09-07)

    Enhancements
    • None.

    ... (truncated)

    Commits
    • c03e2ed Release 1.6.3
    • f75bccc Disable Bazaar tests due to macOS 12.3 not including python2
    • 52a0d54 Merge pull request #128 from CocoaPods/validate_before_dl
    • d27c983 Ensure that the git pre-processor doesn't accidentally bail also
    • 3adfe1f [CHANGELOG] Add empty Master section
    • 591167a Release 1.6.2
    • d2564c3 Merge pull request #127 from CocoaPods/validate_before_dl
    • 99fec61 Switches where we check for invalid input, to move it inside the download fun...
    • 96679f2 [CHANGELOG] Add empty Master section
    • 3a7c54b Release 1.6.1
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • Fixed Warning: Initialization of 'UnsafePointer<CUnsignedChar>' results in a dangling pointer

    Fixed Warning: Initialization of 'UnsafePointer' results in a dangling pointer

    Fixed Warning: Initialization of 'UnsafePointer' (aka 'UnsafePointer') results in a dangling pointer

    This is a new warning added to Xcode 11.4 (See release notes snippet below) See also: https://stackoverflow.com/questions/60861711/initialization-of-unsafemutablerawpointer-results-in-a-dangling-pointer

    Xcode 11.4 Release Notes

    The compiler will now emit a warning when attempting to pass a temporary pointer argument produced from an array, string, or inout argument to a parameter which is known to escape it. This includes the various initializers for the UnsafePointer/UnsafeBufferPointer family of types, as well as memberwise initializers. For example, the compiler will emit warnings compiling the following code: struct S { var ptr: UnsafePointer }

    func foo() { var i: Int8 = 0 let ptr = UnsafePointer(&i) // warning: initialization of 'UnsafePointer' results in a // dangling pointer

    let s1 = S(ptr: [1, 2, 3])
    // warning: passing '[Int8]' to parameter, but argument 'ptr' should be a
    // pointer that outlives the call to 'init(ptr:)'
    
    let s2 = S(ptr: "hello")
    // warning: passing 'String' to parameter, but argument 'ptr' should be a
    // pointer that outlives the call to 'init(ptr:)'
    

    } All 3 of the above examples are unsound because each argument produces a temporary pointer only valid for the duration of the call to which they are passed. Therefore the returned value in each case references a dangling pointer. (SR-2790) https://bugs.swift.org/browse/SR-2790 (31232450)

    opened by GayleDDS 0
  • Save failed snapshot if it differs from reference image

    Save failed snapshot if it differs from reference image

    Proposed solution:

    Save the failed snapshot in the same directory as the reference image and with the same filename but with _failed appended.

    I also considered putting all failed snapshots in a separate directory structure allowing for a simpler .gitignore but I thought this approach might be better because it's less complex and we shouldn't be ignoring the failed snapshots (along with their failing tests) anyhow. Opinions are of course welcome.

    Prepares for https://github.com/parski/SnapshotTest/issues/9 and solves https://github.com/parski/SnapshotTest/issues/46.

    opened by parski 0
Releases(3.0.0)
  • 3.0.0(Sep 3, 2019)

    Changes:

    • Migrate to Swift 5 • Migrate project to Xcode 10.3 • Remove workaround for issue present in earlier versions of Xcode 10

    This release does not break compatibility with Swift 4.2 as no changes to the source have been made. It does however break compatability with earlier versions of Xcode 10, hence the major version bump.

    Source code(tar.gz)
    Source code(zip)
  • 2.1.0(Nov 14, 2018)

  • 2.0.0(Oct 18, 2018)

    Changes:

    • Add support for Swift 4.2 and Xcode 10 • Add documentation regarding principal class and global record mode • Update README asset links to absolute URLs so they render correctly on CocoaPods

    This release breaks compatability with older versions of Swift. Use an older release if you have not updated to Swift 4.2.

    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(May 1, 2018)

Owner
Pär Strindevall
Pär Strindevall
📸 Delightful Swift snapshot testing.

?? SnapshotTesting Delightful Swift snapshot testing. Usage Once installed, no additional configuration is required. You can import the SnapshotTestin

Point-Free 3k Jan 3, 2023
Switchboard - easy and super light weight A/B testing for your mobile iPhone or android app. This mobile A/B testing framework allows you with minimal servers to run large amounts of mobile users.

Switchboard - easy A/B testing for your mobile app What it does Switchboard is a simple way to remote control your mobile application even after you'v

Keepsafe 287 Nov 19, 2022
Upload failing iOS snapshot tests cases to S3

Second Curtain If you're using the cool FBSnapshotTestCase to test your iOS view logic, awesome! Even better if you have continuous integration, like

Ash Furrow 129 Sep 6, 2022
Snapshot view unit tests for iOS

iOSSnapshotTestCase (previously FBSnapshotTestCase) What it does A "snapshot test case" takes a configured UIView or CALayer and uses the necessary UI

Uber Open Source 1.7k Jan 4, 2023
SwiftCheck is a testing library that automatically generates random data for testing of program properties

SwiftCheck QuickCheck for Swift. For those already familiar with the Haskell library, check out the source. For everybody else, see the Tutorial Playg

TypeLift 1.4k Dec 21, 2022
Testing the UI without UI Testing, a Swift experiment.

UI tests without UI Testing experiment This repo is a small experiment to see if there's an "in-between" for testing iOS applications. More feature-le

Joe Masilotti 20 Sep 26, 2022
Swifty tool for visual testing iPhone and iPad apps. Every pixel counts.

Cribble Cribble - a tool for visual testing iPhone and iPad apps. Every pixel counts. Getting Started An example app is included demonstrating Cribble

Max Sokolov 273 Nov 4, 2022
Bluepill is a reliable iOS testing tool that runs UI tests using multiple simulators on a single machine

Bluepill is a tool to run iOS tests in parallel using multiple simulators. Motivation LinkedIn created Bluepill to run its large iOS test suite in a r

Mobile Native Foundation 3.1k Jan 3, 2023
Fastbot is a model-based testing tool for modeling GUI transitions to discover app stability problems

Fastbot is a model-based testing tool for modeling GUI transitions to discover app stability problems. It combines machine learning and reinforcement learning techniques to assist discovery in a more intelligent way.

Bytedance Inc. 446 Dec 29, 2022
Automatic testing of your Pull Requests on GitHub and BitBucket using Xcode Server. Keep your team productive and safe. Get up and running in minutes. @buildasaur

Buildasaur Automatic testing of your Pull Requests on GitHub and BitBucket using Xcode Server. Keep your team productive and safe. Get up and running

Buildasaurs 774 Dec 11, 2022
Implementing and testing In-App Purchases with StoreKit2 in Xcode 13, Swift 5.5 and iOS 15.

StoreHelper Demo Implementing and testing In-App Purchases with StoreKit2 in Xcode 13, Swift 5.5, iOS 15. See also In-App Purchases with Xcode 12 and

Russell Archer 192 Dec 17, 2022
A flexible mock server for automated and regression testing of iOS, Android and other apps.

Note: This document is intended as a quick introduction to Voodoo. As Voodoo has a large number of features, please refer to Voodoo's Github Wiki for

Derek Clarkson 7 Nov 23, 2022
I built this application with unit testing and test-driven development to understand TDD theory and practice

TestDrivenDevelopment Description I built this application with unit testing and test-driven development to understand TDD theory and practice, to wri

null 1 Dec 21, 2021
A Mac and iOS Playgrounds Unit Testing library based on Nimble.

Spry Spry is a Swift Playgrounds Unit Testing library based on Nimble. The best thing about Spry is that the API matches Nimble perfectly. Which means

Quick 327 Jul 24, 2022
Multivariate & A/B Testing for iOS and Mac

This library is no longer being maintained. You can continue to use SkyLab in your projects, but we recommend switching another solution whenever you

Mattt 792 Dec 15, 2022
Remote configuration and A/B Testing framework for iOS

MSActiveConfig v1.0.1 Remote configuration and A/B Testing framework for iOS. Documentation available online. MSActiveConfig at a glance One of the bi

Elevate 78 Jan 13, 2021
Sample project for testing out focus in SwiftUI and iOS 15

This project was to test out different ways of enabling focus in a SwiftUI app.

null 3 Dec 21, 2021
The Swift (and Objective-C) testing framework.

Quick is a behavior-driven development framework for Swift and Objective-C. Inspired by RSpec, Specta, and Ginkgo. // Swift import Quick import Nimbl

Quick 9.6k Dec 31, 2022
UI Testing Cheat Sheet and Examples.

UI Testing Cheat Sheet This repository is complementary code for my post, UI Testing Cheat Sheet and Examples. The post goes into more detail with exa

Joe Masilotti 2.1k Dec 25, 2022