Communityコーディング&開発github.com

スナップショットテスト

Verifyを使用して.NETでスナップショットテストを実施します。APIサーフェス、HTTPレスポンス、レンダリングされたメール、シリアライズ出力を承認し、人間がレビューしたベースラインファイルを通じて意図しない変更を検出します。

スナップショットテスト とは?

このスキルは、Verifyライブラリを使用した.NETアプリケーションのスナップショットテストについて説明しています。スナップショットテストは、テストの出力をキャプチャし、人間が承認したベースライン(.verifiedファイル)と比較することで、意図しない変更を検出します。主なユースケースには、HTMLメールやレンダリング出力の検証、公開APIサーフェスの変更検出、HTTPレスポンスのテスト、シリアライズ出力の検証が含まれます。XUnit、NUnit、MSTestなどのテストフレームワークをサポートし、CI/CDパイプラインへの統合も容易です。動的な値(GUID、タイムスタンプなど)はスクラビング機能でマスクし、毎回のテスト実行で一貫した比較を可能にします。承認された変更はバージョン管理にコミットされ、スナップショットファイルはPRの差分で確認できます。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aaronontheweb/dotnet-skills/tree/main/skills/snapshot-testing

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Verifyを使ったスナップショットテスト

このスキルを使用するタイミング

以下の場合にスナップショットテストを使用します:

  • レンダリングされた出力(HTMLメール、レポート、生成コード)の検証
  • 破壊的変更検出のための公開APIサーフェスの承認
  • HTTPレスポンス本文とヘッダーのテスト
  • シリアライズ出力の検証
  • 複雑なオブジェクトの意図しない変更のキャッチ

スナップショットテストとは

スナップショットテストは出力をキャプチャし、人間が承認したベースラインと比較します:

  1. 初回実行:テストが.received.ファイルに実際の出力を生成
  2. 人間によるレビュー:開発者が承認し、.verified.ファイルを作成
  3. 以降の実行:テストが出力を.verified.ファイルと比較
  4. 変更の検出:テストが失敗し、差分ツールが差異を表示

これにより、意図しない変更をキャッチしつつ、意図的な変更は明示的な承認を通じて許可します。


インストール

Verifyパッケージの追加

dotnet add package Verify.Xunit
# または他のテストフレームワーク用:
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()
    {
        // 検証済みファイルにソースファイル相対パスを使用
        VerifyBase.UseProjectRelativeDirectory("Snapshots");

        // 差分ツールの設定(オプション - 自動検出)
        // 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" });

    // 適切なファイル名のためにextensionパラメーターを使用
    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");

    // ステータス、ヘッダー、本文を同時に検証
    await Verify(new
    {
        StatusCode = response.StatusCode,
        Headers = response.Headers
            .Where(h => h.Key.StartsWith("X-"))  // カスタムヘッダーのみ
            .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(),  // 実行ごとに異なる
        CreatedAt = DateTime.UtcNow,  // 実行ごとに異なる
        Total = 99.99m
    };

    return Verify(order)
        .ScrubMember("Id")  // プレースホルダーに置き換え
        .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>();

    // 特定のパターンをスクラブ
    VerifierSettings.AddScrubber(s =>
        Regex.Replace(s, @"token=[a-zA-Z0-9]+", "token=SCRUBBED"));
}

ファイル構成

推奨構成

tests/
  MyApp.Tests/
    Snapshots/           # すべての検証済みファイル
      EmailTests/
        WelcomeEmail.verified.html
        PasswordReset.verified.html
      ApiTests/
        GetUser.verified.txt
    EmailTests.cs
    ApiTests.cs
    ModuleInitializer.cs

.gitignore

# Verify - receivedファイルは無視(verifiedのみをコミット)
*.received.*

.gitattributes

# 検証済みファイルを生成物として扱う(PR差分で折りたたむ)
*.verified.txt linguist-generated=true
*.verified.html linguist-generated=true
*.verified.json linguist-generated=true

CI/CD統合

検証済みファイルがない場合に失敗

[ModuleInitializer]
public static void Init()
{
    // CIでは、差分ツールを起動せずに失敗させる
    if (Environment.GetEnvironmentVariable("CI") == "true")
    {
        VerifyDiffPlex.UseDiffPlex(OutputType.Minimal);
        DiffRunner.Disabled = true;
    }
}

GitHub Actions

- name: テストを実行
  run: dotnet test
  env:
    CI: true

- name: 失敗時にスナップショットをアップロード
  if: failure()
  uses: actions/upload-artifact@v4
  with:
    name: snapshots
    path: |
      **/*.received.*
      **/*.verified.*

スナップショットテストを使用するタイミング

シナリオスナップショットテストを使用するか?理由
レンダリングされたHTML/メールはいビジュアルリグレッションをキャッチ
APIサーフェスはい偶発的な破壊を防止
シリアライズ出力はいワイヤーフォーマットを検証
複雑なオブジェクトグラフはい手動アサーションより簡単
単純な値のチェックいいえ通常のアサーションを使用
ビジネスロジックいいえ明示的なアサーションを使用
パフォーマンステストいいえベンチマークを使用

ベストプラクティス

推奨事項

// テスト名に説明を記載 - それがファイル名になる
[Fact]
public Task UserRegistration_WithValidData_ReturnsConfirmation()

// 動的な値を一貫してスクラブ
VerifierSettings.ScrubMembersWithType<Guid>();

// テキスト以外のコンテンツにはextensionパラメーターを使用
await Verify(html, extension: "html");

// 検証済みファイルをソース管理に保持
git add *.verified.*

禁止事項

// スクラブなしでランダム/動的データを検証しない
var order = new Order { Id = Guid.NewGuid() };  // 毎回失敗!
await Verify(order);

// .receivedファイルをコミットしない
git add *.received.*  // 間違い!

// 単純なアサーションには使用しない
await Verify(result.Count);  // 単に Assert.Equal(5, result.Count) を使用すべき

MJMLメールテストとの統合

完全なパターンについては、aspnetcore/transactional-emailsスキルを参照:

  1. {{variable}}プレースホルダー付きのMJMLテンプレート
  2. テストデータでHTMLにレンダリング
  3. レンダリングされた出力をスナップショットテスト
  4. 承認前に差分ツールで変更をレビュー

これにより以下をキャッチ:

  • 壊れた変数置換
  • CSS/レイアウトのリグレッション
  • メールクライアントの互換性問題
  • 意図しないコンテンツ変更

参考資料

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

akka-hosting-actor-patterns

Patterns for building entity actors with Akka.Hosting - GenericChildPerEntityParent, message extractors, cluster sharding abstraction, akka-reminders, and ITimeProvider. Supports both local testing and clustered production modes.

akka-net-aspire-configuration

Configure Akka.NET with .NET Aspire for local development and production deployments. Covers actor system setup, clustering, persistence, Akka.Management integration, and Aspire orchestration patterns.

akka-net-best-practices

Critical Akka.NET best practices including EventStream vs DistributedPubSub, supervision strategies, error handling, Props vs DependencyResolver, work distribution patterns, and cluster/local mode abstractions for testability.

akka-net-management

Akka.Management for cluster bootstrapping, service discovery (Kubernetes, Azure, Config), health checks, and dynamic cluster formation without static seed nodes.

akka-net-testing-patterns

Write unit and integration tests for Akka.NET actors using modern Akka.Hosting.TestKit patterns. Covers dependency injection, TestProbes, persistence testing, and actor interaction verification. Includes guidance on when to use traditional TestKit.

api-design

Design stable, compatible public APIs using extend-only design principles. Manage API compatibility, wire compatibility, and versioning for NuGet packages and distributed systems.

aspire-configuration

Configure Aspire AppHost to emit explicit app config via environment variables; keep app code free of Aspire clients and service discovery.

aspire-integration-testing

Write integration tests using .NET Aspire

aspire-service-defaults

Create a shared ServiceDefaults project for Aspire applications. Centralizes OpenTelemetry, health checks, resilience, and service discovery configuration across all services.

crap-analysis

Analyze code coverage and CRAP (Change Risk Anti-Patterns) scores to identify high-risk code. Use OpenCover format with ReportGenerator for Risk Hotspots showing cyclomatic complexity and untested code paths.

csharp-concurrency-patterns

Choosing the right concurrency abstraction in .NET - from async/await for I/O to Channels for producer/consumer to Akka.NET for stateful entity management. Avoid locks and manual synchronization unless absolutely necessary.

database-performance

Database access patterns for performance. Separate read/write models, avoid N+1 queries, use AsNoTracking, apply row limits, and never do application-side joins. Works with EF Core and Dapper.

dependency-injection-patterns

Organize DI registrations using IServiceCollection extension methods. Group related services into composable Add* methods for clean Program.cs and reusable configuration in tests.

dotnet-devcert-trust

Diagnose and fix .NET HTTPS dev certificate trust issues on Linux. Covers the full certificate lifecycle from generation to system CA bundle inclusion, with distro-specific guidance for Ubuntu, Fedora, Arch, and WSL2.

dotnet-local-tools

Managing local .NET tools with dotnet-tools.json for consistent tooling across development environments and CI/CD pipelines.

dotnet-project-structure

Modern .NET project structure including .slnx solution format, Directory.Build.props, central package management, SourceLink, version management with RELEASE_NOTES.md, and SDK pinning with global.json.

dotnet-slopwatch

Use Slopwatch to detect LLM reward hacking in .NET code changes. Run after every code modification to catch disabled tests, suppressed warnings, empty catch blocks, and other shortcuts that mask real problems.

efcore-patterns

Entity Framework Core best practices including NoTracking by default, query splitting for navigation collections, migration management, dedicated migration services, and common pitfalls to avoid.

ilspy-decompile

Understand implementation details of .NET code by decompiling assemblies. Use when you want to see how a .NET API works internally, inspect NuGet package source, view framework implementation, or understand compiled .NET binaries.

mailpit-integration

Test email sending locally using Mailpit with .NET Aspire. Captures all outgoing emails without sending them. View rendered HTML, inspect headers, and verify delivery in integration tests.

関連スキル