Snapshot Testing Tutorial for SwiftUI: Getting Started

Learn how to test your SwiftUI iOS views in a simple and fast way using snapshot testing. By Vijay Subrahmanian.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Introducing a UI Change

From now on, when you run your test case, SnapshotTesting will take a new snapshot image of your view and compare it to the baseline snapshot. As long as your view code does not change, your test will pass.

But this is software — things always change!

For example, assume the app’s designer wants to add a release date to each book. To see what happens, open BookRowView.swift. Find // Insert release Text and add this code below the comment:

Text(book.release)
  .font(.subheadline)
  .foregroundColor(.secondary)

Here, you added a Swift UI Text element that shows the release date of the book.

Build and run the project using Command-R. You’ll see the newly added release date information alongside the publisher name.

Update UI of Book List with release date

Now, see what the test has to say about this update.

Build and run the test using Command-U. The test fails with an error:

SnapshotTesting failure because snapshot does not match baseline reference

SnapshotTesting reports: Newly-taken snapshot does not match reference. This means that the baseline snapshot, without release dates, doesn’t match the new snapshot, which includes the release dates.

The error message also conveniently includes the paths of the baseline snapshot and the new snapshot. Copy the paths of each and open them in Finder to see the difference.

Note: In Mac’s Finder app, you can go to a specific path by pressing Command-Shift-G and then pasting in the path.

Here’s the baseline snapshot:

Previously saved baseline snapshot

And here’s the new snapshot:

New snapshot to compare with the baseline snapshot

Of course, you expected this behavior. After all, you just changed the UI yourself! So to get your test case to pass, you’ll have to update your baseline snapshot.

Updating the Baseline Snapshot

Open BookRowViewTests.swift and add the following line just above assertSnapshot(matching:as:):

isRecording = true

When you set isRecording to true, it tells the SnapshotTesting framework that you want to create a new baseline snapshot.

Build and run the test.

SnapshotTesting reports no reference snapshot in error message

Your test fails, but SnapshotTesting saved a new baseline snapshot, just like the first time you ran your test.

Now that you have a new baseline snapshot, remove the line isRecording = true again.

Build and run the test case again. Voila! The test passes.

Snapshot Test success

You now know how to create a baseline snapshot, and you know how to update it when you make a UI change. But remember, if you see a failed test case when you didn’t intentionally change your UI, be sure to fix the code and not the test case! :]

Testing the Detail View

Now that you’ve thoroughly tested your book row view, you’ll test your book details screen. As you’ll see, the process is similar. However, unlike the previous test, where the system under test was a UIView, you’ll test a UIViewController here.

With a UIViewController, you can set your tests to use different iPhone versions, different screen orientations, different device types and even check how your app works in dark mode.

Setting up Your Testing Environment

With so many different testing options and variations, you might think that you’ll have to create your detail view object over and over again. Fortunately, SnapshotTesting offers a better way to create the system under test.

Open BookDetailViewTests.swift. Add the following code below // Setup - creating an instance of the BookDetailView:

override func setUpWithError() throws {
  try super.setUpWithError()
  let bookDetailView = BookDetailView(book: sampleBook)
  viewController = UIHostingController(rootView: bookDetailView)
}

This code creates an instance of BookDetailView and adds it to UIHostingController as a root view, just as you did in BookRowViewTests. However, by putting this code in setUpWithError(), SnapshotTesting will automatically run this code before each test case. That way, you don’t have to create your system under test in your test case functions.

Now find // Tear down - Clear any instance variables, and add the following below:

override func tearDownWithError() throws {
  try super.tearDownWithError()
  viewController = nil
}

Here, you clear the value of viewController after each test run. That way, each test case can start with a brand new BookDetailView.

Build and run the tests. Success! The tests pass, though you haven’t made any assertions yet.

Test Success without any assertions

Testing for Specific iPhone Versions

Find testBookDetailViewOniPhone(), then add the following line to the function body:

assertSnapshot(
  matching: viewController,
  as: .image(on: .iPhoneX))

image(on:) accepts an argument of type ViewImageConfig, which is set to .iPhoneX here. This tells SnapshotTesting to render the viewController at an iPhone X screen size. The default value for orientation is portrait.

Build and run the individual test by clicking the Diamond icon in the gutter.

iPhone Portrait test failure

The test failed, but you’ve now recorded and saved the baseline snapshot.

Run the test again and it will succeed.

iPhone Portrait test success

Note: You can choose a device to test the view against from a comprehensive list that the Snapshot Testing framework provides.

Testing for Device Orientation

Similarly, find testBookDetailViewOniPhoneLandscape() and add the following code to its body:

assertSnapshot(
  matching: viewController,
  as: .image(on: .iPhoneX(.landscape)))

This time, you test your UI on the iPhone X in landscape orientation by passing .landscape to the device object.

Next, find testBookDetailViewOniPadPortrait() and add this code to its body:

assertSnapshot(
  matching: viewController,
  as: .image(on: .iPadPro11(.portrait)))

This function tests the UI on an iPad Pro 11 in portrait orientation. Here, ViewImageConfig is set to .iPadPro11 with a .portrait Orientation. Simple!

Build and run the tests. The test fails because there was no baseline.

iPad testing failed

Build and run — now, it succeeds.

iPad testing success

Testing for Dark Mode

As previously mentioned, you can also test how your app behaves with Dark Mode.

Find testBookDetailViewOniPhoneDarkMode() and add the following code to its body:

let traitDarkMode = UITraitCollection(userInterfaceStyle: .dark)
assertSnapshot(
  matching: viewController,
  as: .image(on: .iPhoneX, traits: traitDarkMode))

First, you create UITraitCollection, which sets the UI style to dark mode.

You then pass that trait collection to image(on:traits:), which tells SnapshotTesting to apply dark mode when taking a snapshot.

Note: You can combine a list of UITraitCollections into a single trait collection, which opens up a way to validate a multitude of combinations!

Now, build and run your test. As you saw earlier, the tests fail when there’s no baseline reference of the snapshot.

iPhone dark mode failed

Again, build and run — all the tests pass!

Snapshot testing Book Details Succeeds

Next time you make a change in the code, you can be sure that the change looks fine on all devices just by running the test cases!