@AddPreviews
makes preview-based snapshot tests easier.
When applied to a preview provider it...
- Automatically creates a
preview
by deriving the view's contents from static view properties within the struct. - Makes the preview provider iterable through its view properties, facilitating snapshot coverage of each preview state.
Just import AddPreviews
and attach @AddPreviews
to your preview struct.
import AddPreviews
@AddPreviews
struct MyView_Previews: PreviewProvider {
static var stateOne: some View { MyView(state: .one) }
static var stateTwo: some View { MyView(state: .two) }
}
This will generate a previews
property containing each of your view states, along with display names to easily identify which one you're looking at in Xcode:
// (Generated)
static var previews: some View {
stateOne.previewDisplayName("stateOne")
stateTwo.previewDisplayName("stateTwo")
}
The real magic comes in the boilerplate removal in snapshot tests.
@AddPreviews
makes an annotated preview provider iterable over each of its view properties, allowing a snapshot test to be reduced from this:
import SnapshotTesting
import XCTest
final class MyViewTests: XCTestCase {
func testStateOne() {
assertSnapshot(of: MyView_Previews.stateOne, as: .image(layout: .device(config: .yourDevice)))
}
func testStateTwo() {
assertSnapshot(of: MyView_Previews.stateTwo, as: .image(layout: .device(config: .yourDevice)))
}
}
To this - code that effortlessly scales with the addition of new preview states:
import SnapshotTesting
import XCTest
final class MyViewTests: XCTestCase {
func testPreviews() {
for preview in MyView_Previews() {
assertSnapshot(of: preview, as: .image(layout: .device(config: .yourDevice)), named: preview.name)
}
}
}
All you have to do is rerecord snapshots when making an addition or change, or remove unused reference images when removing a preview state.
Why create specific view properties? Why not just inline different states in the preview property itself?
This pattern makes writing snapshot tests for device-sized views a breeze, as shown above!
That said, this pattern is best-tailored for screen-sized views that should have their own reference images. For smaller views, this approach is overkill, and a preview comprising of multiple states in a stack could just be simply written and snapshotted directly, as shown below:
Preview:
struct RowView_Previews: PreviewProvider {
static var previews: some View {
VStack {
RowView(title: "Title")
RowView(title: "Title", subtitle: "Subtitle")
RowView(title: "Title", subtitle: "Subtitle") {
Image(systemSymbol: .envelopeFill)
}
}
}
}
Snapshot:
final class RowViewTests: XCTestCase {
func testPreviews() {
assertSnapshot(of: RowView_Previews.previews, as: .image(layout: .device(config: .yourDevice)))
}
}
#Preview is nice, concise, and is positioned as the future of Xcode previews, but it doesn't support snapshot testing in the way that PreviewProvider
does, as shown above.
Using #Preview
generates a struct with a mangled type name like this:
$s17<YourTarget>33_5594AE1E7369B73F633885FC4E970BA7Ll7PreviewfMf_15PreviewRegistryfMu_
While it's technically possible to reference this type name (though ill-advised), there's still no View
that can be extracted out from it that can be plugged into a snapshot test.
DeveloperToolsSupport is currently a black-box in regards to how Xcode turns these previews into views.
This library is released under the MIT license. See LICENSE for details.