SnapshotTestingMacros adds snapshot assertions and snapshot-specific traits to Swift Testing while continuing to use swift-snapshot-testing as the snapshot engine under the hood.
The preferred API is native Swift Testing:
@Suite@Test#expectSnapshot(...)- Snapshot traits such as
.theme(...),.sizes(...),.padding(...),.record(...), and.strategy(...)
The legacy @SnapshotSuite and @SnapshotTest macros remain available as a migration surface, but they are deprecated.
import SnapshotTestingMacros
import SwiftUI
import Testing
@MainActor
@Suite(.theme(.all), .sizes(.minimum))
struct ProfileCardSnapshots {
@Test
func profileCard() {
#expectSnapshot(ProfileCard())
}
}@MainActor on the suite is worth adding from the start, and every form works from it: snapshot
values and builders are main-actor isolated, so a test can reach main-actor state — a view model,
a @MainActor factory — directly inside #expectSnapshot, with or without async. Nonisolated
suites work too and get the same reach, because the builder carries the isolation rather than the
call site.
swift test needs nothing special. Building through xcodebuild does:
xcodebuild test -scheme YourScheme -destination 'platform=macOS' -skipMacroValidationWithout -skipMacroValidation, xcodebuild refuses to expand the macro until it has been
approved — "Macro 'SnapshotsMacros' … must be enabled before it can be used". Locally that is
a Trust & Enable prompt in Xcode. On CI there is nobody to click it, so the flag is required
rather than optional.
iOS 15+ and macOS 15+ only. watchOS, tvOS, and visionOS are not supported; building the package for those platforms fails with an explicit compile-time error.
| Surface | Support |
|---|---|
| SwiftUI | Direct-value snapshots, named:, @ViewBuilder closure forms, SnapshotConfiguration, and argument: helpers |
| UIKit / AppKit | Direct values plus sync, throwing, async, and async-throwing closure, SnapshotConfiguration, and argument: snapshots for views and view controllers |
Every builder is main-actor isolated, SwiftUI and platform alike. A direct value carries whatever effects its expression
has — try #expectSnapshot(try makeView()), await #expectSnapshot(await makeView()) and
try await #expectSnapshot(try await makeView()) all compose, and a try nested inside a larger expression counts.
Throwing builders rethrow their factory and snapshot-pipeline errors.
For UIKit and AppKit, keep the test itself as a regular @Test unless using an async builder, and pass a
helper-backed expression such as #expectSnapshot(makeViewController()). Parameterised builders use
argument: or SnapshotConfiguration in the same way as SwiftUI.
Adopters moving from @SnapshotSuite / @SnapshotTest to native @Suite / @Test / #expectSnapshot(...) should use the migrator, which lives in its own repository because it is a one-time tool:
swift-snapshot-testing-macros-migrator
git clone https://github.com/adammcarter/swift-snapshot-testing-macros-migrator
cd swift-snapshot-testing-macros-migrator
Tools/migrate-snapshot-tests --project-root /path/to/consumer-repo # dry run
Tools/migrate-snapshot-tests --project-root /path/to/consumer-repo --applyIt rewrites the sources and renames the checked-in references in the same run. See MIGRATION.md for the mapping, and that repository for the full guide — in particular what changes about macOS reference images and why you re-record once.
For local setup and detailed contributor guidance, see CONTRIBUTING.md.
Common commands:
mise run lint
swift testFor fast local iteration, prefer the focused unit suites CI also uses across Xcode versions:
swift test --filter ExpectSnapshotAdapterTests
swift test --filter ExpectSnapshotMacroTests
swift test --filter SnapshotSuiteTests
swift test --filter SnapshotTestTestsxcodebuild test \
-scheme SnapshotsUnitTests \
-destination 'platform=macOS'Integration tests render against committed references, so the Xcode and simulator destination are
pinned once in mise.toml; run them through the mise task so they always use that configuration:
mise run test-integrationSnapshot references are bound to the recording environment (Xcode and macOS), so if your machine differs from CI you cannot produce matching references locally. Instead, run the Regenerate Snapshot References workflow (Actions → Run workflow) on your branch — it re-records everything on the CI runner and commits the result onto your branch. See CONTRIBUTING.md for the full flow.
Latest-Xcode CI also runs fast macOS build-for-testing smoke checks on 26.4, 26.5, and 26.6:
xcodebuild build-for-testing \
-scheme SnapshotsUnitTests \
-destination 'platform=macOS'
xcodebuild build-for-testing \
-scheme SnapshotsIntegrationTests \
-destination 'platform=macOS'