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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Snapshot Testing Tutorial for SwiftUI: Getting Started
15 mins
- Getting Started
- What Is Snapshot Testing?
- Snapshot Testing Strategies
- Using the Image Strategy
- Testing the Row View
- Generating a Baseline Snapshot
- Introducing a UI Change
- Updating the Baseline Snapshot
- Testing the Detail View
- Setting up Your Testing Environment
- Testing for Specific iPhone Versions
- Testing for Device Orientation
- Testing for Dark Mode
- Finding the Baseline Snapshots
- Where to Go From Here?
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.
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 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.
Here’s the baseline snapshot:
And here’s the new 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.
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.
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.
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.
The test failed, but you’ve now recorded and saved the baseline snapshot.
Run the test again and it will succeed.
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.
Build and run — now, it succeeds.
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.
UITraitCollection
s 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.
Again, build and run — all the tests pass!
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!