snapshot-testing について
snapshot-testing は、Verify を使う .NET 開発者向けのスキルです。人が承認した基準出力とテスト結果を比較し、描画内容、公開 API、HTTP レスポンス、シリアライズ結果を扱います。
主な機能
- 01承認済み出力のベースライン
- 02HTML とオブジェクトのスナップショット
- 03公開 API の変更検出
- 04動的な値のスクラブ
- 05CI でのスナップショット検査
活用シーン
- 01描画されたメールの変更確認
- 02意図しない公開 API の破壊的変更検出
- 03HTTP レスポンスとシリアライズ結果の検証
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ aaronontheweb/ dotnet-skills --skill snapshot-testingインストール後、Claude Code で/snapshot-testingを入力して読み込まれたか確認します。
詳細
Verify を使ったスナップショットテスト
このスキルを使う場面
次のような場合にスナップショットテストを使います。
- レンダリングされた出力(HTML メール、レポート、生成コード)を検証する
- 破壊的変更を検出するために、公開 API のサーフェスを承認する
- HTTP レスポンスのボディとヘッダーをテストする
- シリアル化された出力を検証する
- 複雑なオブジェクトに意図しない変更がないか確認する
スナップショットテストとは?
スナップショットテストでは、出力を取得し、人が承認したベースラインと比較します。
- 初回実行:テストによって実際の出力を含む
.received.ファイルが生成されます - 人によるレビュー:開発者が内容を承認し、
.verified.ファイルを作成します - 以降の実行:テストで出力を
.verified.ファイルと比較します - 変更の検出:テストが失敗し、差分ツールにレビュー用の差分が表示されます
これにより、意図しない変更を検出しつつ、意図的な変更は明示的な承認を経て反映できます。
インストール
Verify パッケージを追加する
dotnet add package Verify.Xunit
# or for other test frameworks:
dotnet add package Verify.NUnit
dotnet add package Verify.MSTest
ModuleInitializer を設定する
テストプロジェクトに ModuleInitializer.cs を作成します。
using System.Runtime.CompilerServices;
public static class ModuleInitializer
{
[ModuleInitializer]
public static void Init()
{
// Use source-file-relative paths for verified files
VerifyBase.UseProjectRelativeDirectory("Snapshots");
// Configure diff tool (optional - auto-detected)
// DiffTools.UseOrder(DiffTool.Rider, DiffTool.VisualStudioCode);
}
}
基本的な使い方
シンプルなオブジェクトの検証
[Fact]
public Task VerifyUserDto()
{
var user = new UserDto(
Id: "user-123",
Name: "John Doe",
Email: "[email protected]",
CreatedAt: new DateTime(2025, 1, 15));
return Verify(user);
}
次のファイルが作成されます:VerifyUserDto.verified.txt
{
Id: user-123,
Name: John Doe,
Email: [email protected],
CreatedAt: 2025-01-15T00:00:00
}
文字列/HTML の検証
[Fact]
public async Task VerifyRenderedEmail()
{
var html = await _emailRenderer.RenderAsync("Welcome", new { Name = "John" });
// Use extension parameter for proper file naming
await Verify(html, extension: "html");
}
ブラウザーで表示できる VerifyRenderedEmail.verified.html が作成されます。
メールテンプレートのテスト
Verify を使って、レンダリングされたメールテンプレートに意図しない変更がないか確認します。
[Fact]
public async Task UserSignupInvitation_RendersCorrectly()
{
var renderer = _services.GetRequiredService<IMjmlTemplateRenderer>();
var variables = new Dictionary<string, string>
{
{ "OrganizationName", "Acme Corporation" },
{ "InviteeName", "John Doe" },
{ "InviterName", "Jane Admin" },
{ "InvitationLink", "https://example.com/invite/abc123" },
{ "ExpirationDate", "December 31, 2025" }
};
var html = await renderer.RenderTemplateAsync(
"UserInvitations/UserSignupInvitation",
variables);
await Verify(html, extension: "html");
}
メールテストでのメリット:
- CSS やレイアウトのリグレッションを検出します
- テンプレート変数の破損を検出します
- 差分ツールで視覚的にレビューできます
- バージョン管理でメールの変更を追跡できます
API サーフェスの承認
公開 API に意図しない破壊的変更が生じるのを防ぎます。
[Fact]
public Task ApprovePublicApi()
{
var assembly = typeof(MyLibrary.PublicClass).Assembly;
var publicApi = assembly.GetExportedTypes()
.OrderBy(t => t.FullName)
.Select(t => new
{
Type = t.FullName,
Members = t.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
.Where(m => m.DeclaringType == t)
.OrderBy(m => m.Name)
.Select(m => m.ToString())
});
return Verify(publicApi);
}
または、専用の ApiApprover パッケージを使います。
dotnet add package PublicApiGenerator
dotnet add package Verify.Xunit
[Fact]
public Task ApproveApi()
{
var api = typeof(MyPublicClass).Assembly.GeneratePublicApi();
return Verify(api);
}
API サーフェス全体を含む .verified.txt が作成されます。変更にはすべて明示的な承認が必要です。
HTTP レスポンスのテスト
[Fact]
public async Task GetUser_ReturnsExpectedResponse()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/api/users/123");
// Verify status, headers, and body together
await Verify(new
{
StatusCode = response.StatusCode,
Headers = response.Headers
.Where(h => h.Key.StartsWith("X-")) // Custom headers only
.ToDictionary(h => h.Key, h => h.Value.First()),
Body = await response.Content.ReadAsStringAsync()
});
}
動的な値のスクラブ
タイムスタンプ、GUID、その他の動的なコンテンツを処理します。
[Fact]
public Task VerifyOrder()
{
var order = new Order
{
Id = Guid.NewGuid(), // Different every run
CreatedAt = DateTime.UtcNow, // Different every run
Total = 99.99m
};
return Verify(order)
.ScrubMember("Id") // Replace with placeholder
.ScrubMember("CreatedAt");
}
出力:
{
Id: Guid_1,
CreatedAt: DateTime_1,
Total: 99.99
}
グローバルなスクラブ
ModuleInitializer で設定します。
[ModuleInitializer]
public static void Init()
{
VerifierSettings.ScrubMembersWithType<DateTime>();
VerifierSettings.ScrubMembersWithType<DateTimeOffset>();
VerifierSettings.ScrubMembersWithType<Guid>();
// Scrub specific patterns
VerifierSettings.AddScrubber(s =>
Regex.Replace(s, @"token=[a-zA-Z0-9]+", "token=SCRUBBED"));
}
ファイル構成
推奨される構成
tests/
MyApp.Tests/
Snapshots/ # All verified files
EmailTests/
WelcomeEmail.verified.html
PasswordReset.verified.html
ApiTests/
GetUser.verified.txt
EmailTests.cs
ApiTests.cs
ModuleInitializer.cs
.gitignore
# Verify - ignore received files (only commit verified)
*.received.*
.gitattributes
# Treat verified files as generated (collapse in PR diffs)
*.verified.txt linguist-generated=true
*.verified.html linguist-generated=true
*.verified.json linguist-generated=true
CI/CD との統合
検証済みファイルがない場合に失敗させる
[ModuleInitializer]
public static void Init()
{
// In CI, fail instead of launching diff tool
if (Environment.GetEnvironmentVariable("CI") == "true")
{
VerifyDiffPlex.UseDiffPlex(OutputType.Minimal);
DiffRunner.Disabled = true;
}
}
GitHub Actions
- name: Run tests
run: dotnet test
env:
CI: true
- name: Upload snapshots on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: snapshots
path: |
**/*.received.*
**/*.verified.*
スナップショットテストを使う場面
| シナリオ | スナップショットテストを使うか? | 理由 |
|---|---|---|
| レンダリングされた HTML/メール | はい | 視覚的なリグレッションを検出します |
| API サーフェス | はい | 意図しない破壊的変更を防ぎます |
| シリアル化された出力 | はい | ワイヤーフォーマットを検証します |
| 複雑なオブジェクトグラフ | はい | 手動のアサーションより簡単です |
| シンプルな値のチェック | いいえ | 通常のアサーションを使います |
| ビジネスロジック | いいえ | 明示的なアサーションを使います |
| パフォーマンステスト | いいえ | ベンチマークを使います |
ベストプラクティス
すること
// Use descriptive test names - they become file names
[Fact]
public Task UserRegistration_WithValidData_ReturnsConfirmation()
// Scrub dynamic values consistently
VerifierSettings.ScrubMembersWithType<Guid>();
// Use extension parameter for non-text content
await Verify(html, extension: "html");
// Keep verified files in source control
git add *.verified.*
しないこと
// Don't verify random/dynamic data without scrubbing
var order = new Order { Id = Guid.NewGuid() }; // Fails every run!
await Verify(order);
// Don't commit .received files
git add *.received.* // Wrong!
// Don't use for simple assertions
await Verify(result.Count); // Just use Assert.Equal(5, result.Count)
MJML メールテストとの統合
完全なパターンについては、mjml-email-templates および verify-email-snapshots スキルを参照してください。
{{variable}}プレースホルダーを使った MJML テンプレート- テストデータを使って HTML にレンダリング
- レンダリングされた出力をスナップショットテスト
- 承認前に差分ツールで変更をレビュー
次の問題を検出できます。
- 変数置換の破損
- CSS やレイアウトのリグレッション
- メールクライアントとの互換性の問題
- 意図しないコンテンツの変更
リソース
- Verify GitHub:https://github.com/VerifyTests/Verify
- Verify.Xunit:https://github.com/VerifyTests/Verify.Xunit
- ApiApprover:https://github.com/JakeGinnivan/ApiApprover
- DiffPlex Integration:https://github.com/VerifyTests/Verify.DiffPlex
コメント
Markdown 対応