swift-snapshot-testing-macros

4.0.0

A Swift Macro library for generating snapshot tests from functions
adammcarter/swift-snapshot-testing-macros

What's New

4.0.0

2026-08-16T23:03:58Z

📰 Native Swift Testing

Snapshot Testing Macros now uses Swift Testing as standard and acts as a helper library to pass traits and expectations via the same (or similar) shape to Swift Testing.

What's Changed

  • Native #expectSnapshot, replacing the @SnapshotSuite / @SnapshotTest macros by @adammcarter in #100
  • Removed exludes from package files by @adammcarter in #99

Full Changelog: 3.0.0...4.0.0

Snapshot Tests

SnapshotTestingMacros

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.

Quick start

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.

Running under xcodebuild

swift test needs nothing special. Building through xcodebuild does:

xcodebuild test -scheme YourScheme -destination 'platform=macOS' -skipMacroValidation

Without -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.

Supported platforms

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.

Supported native surface

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.

Documentation

Migration

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 --apply

It 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.

Development

For local setup and detailed contributor guidance, see CONTRIBUTING.md.

Common commands:

mise run lint
swift test

For 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 SnapshotTestTests
xcodebuild 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-integration

Snapshot 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'

Description

  • Swift Tools 6.2.0
View More Packages from this Author

Dependencies

Last updated: Sun Aug 23 2026 08:59:36 GMT-0900 (Hawaii-Aleutian Daylight Time)