dafurman/AddPreviews
A Swift macro that makes preview-based snapshot testing easier
Usage
Use in Previews
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")
}Use in Snapshot Tests
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.
Motivation
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)))
}
}What about the `#Preview` macro?
#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.
License
This library is released under the MIT license. See LICENSE for details.
Package Metadata
Repository: dafurman/AddPreviews
Stars: 1
Forks: 0
Open issues: 0
Default branch: main
Primary language: swift
License: MIT
Topics: generated, macros, previews, snapshots, swift, tests
README: README.md