Prefire を使って SwiftUI の #Preview からスナップショットテストを自動生成する

背景

Xcode 15 / Swift 5.9 で #Preview マクロが登場し、SwiftUI の Preview の書き方が大きく変わりました。

// 以前の書き方
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

// #Preview マクロ
#Preview {
    ContentView()
}

記述がシンプルになったのは嬉しいのですが、一方で困ったこともありました。Preview をスナップショットテストに活用しようとしたとき、PreviewProvider はプロトコル準拠なので Sourcery のような静的解析ツールで型情報として検知できていたのに対し、#Preview はマクロなのでコンパイル時に展開されるため同じアプローチが使えなくなりました。

SourceryPreview リポジトリでの試行錯誤

2 年前、#Preview を検知できる Prefire というライブラリを見つけて試行錯誤していました。最近改めて使い直してみたので、その内容をまとめます。

Prefire とは

Prefire は SwiftUI の Preview から自動的にスナップショットテストを生成するライブラリです。SPM Build Plugin として動作し、swift-snapshot-testing と連携します。

#Preview の検知は content.contains("#Preview") によるテキストマッチで行われ、波括弧のバランスを追跡してブロック全体を抽出しているようです。

設定方法

実際に Prefire をプロジェクトに導入する手順を見ていきましょう。

Package.swift での依存関係追加

Package.swift に Prefire と swift-snapshot-testing を追加し、SnapshotTests テストターゲットを作成します。

.testTarget(
    name: "SnapshotTests",
    dependencies: [
        "AppFeature",
        "SharedUI",
        .product(name: "Prefire", package: "Prefire"),
        .product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
    ],
    plugins: [
        .plugin(name: "PrefireTestsPlugin", package: "Prefire"),
    ]
)

PrefireTestsPlugin を plugins に指定するのがポイントです。このプラグインがビルド時に Preview を検出してテストコードを自動生成します。

.prefire.yml の設定

.prefire.yml で対象のソースディレクトリやインポートするモジュールを設定します。

test_configuration:
  target: AppFeature
  sources:
    - ${PACKAGE_DIR}/Sources/AppFeature
    - ${PACKAGE_DIR}/Sources/SharedUI
    - ${PACKAGE_DIR}/Sources/ListFeature
    - ${PACKAGE_DIR}/Sources/DetailFeature
  imports:
    - AppFeature
    - SharedUI
    - ListFeature
    - DetailFeature
  snapshot_devices:
    - iPhone 17 Pro Max

ここで注意点があります。sources: のパスには ${PACKAGE_DIR}/ プレフィックスが必要です。Xcode のサンドボックス環境ではカレントディレクトリが / になるため、相対パスだとファイルが見つかりません。このプレフィックスを付けることで SPM パッケージのルートディレクトリからの正しいパスが解決されます。

これで #Preview の内容が検知され、スナップショット画像が自動生成されます。

PR 上でスナップショット差分を確認する

Prefire の機能と組み合わせて、ユニットテストで失敗した時に差分が PR でわかるようにしました。GitHub Actions でスナップショット変更を検出し、Before / After / Diff の 3 列テーブルを PR コメントに投稿しています。

PR 上での Snapshot Changes コメント

まとめ

Prefire は #Preview マクロをテキストベースの行スキャンで検出し、Preview からスナップショットテストを自動生成してくれます。.prefire.ymlsources:${PACKAGE_DIR}/ プレフィックスを付けることで、マルチモジュール SPM 構成でも導入できます。GitHub Actions と組み合わせると、Preview の変更がレビュー時に視覚的に確認できるようになります。現在では UIKit でも #Preview の利用ができるため、UIKit / SwiftUI 問わずお試しください。