第29章:テスト③:統合テスト(DI構成で通し確認)🧩🏁
この章のゴール🎯✨
統合テストで、次の「つながり」をまとめて確認できるようになります🙂🔗
- DI登録が正しい(解決できる)🧰✅
- ユースケース実行 → 集約の状態変更 → ドメインイベント発生 🔔
- イベントがディスパッチされて、ハンドラがちゃんと動く📣➡️🎯
- 「登録ミス」「配信漏れ」をテストで早期発見🔎💥
統合テストは、単体テストより広く、複数コンポーネントが一緒に動くことを確認するテストです🙂 (Microsoft Learn)
統合テストは“ここまで”やる(やりすぎ防止)🚧😇
統合テストは、全部を網羅しなくてOKです。重要な少数ケースに絞るのがコツ🙂✨
3段階のうち、まずは真ん中を狙う🎯
- 単体テスト(第27章):ドメインがイベントを出すか🧪🔔
- ハンドラ単体テスト(第28章):ハンドラが副作用を起こすか🧪🎭
- 統合テスト(第29章):DI + ディスパッチの配線が全部つながるか🧩🏁 ← 今ここ!
29.1 DIの配線テスト:依存関係の整合性🔌🧼

DIコンテナに必要なクラス(インターフェースの実装)がすべて正しく登録されているかを確認します。
今回の「最小E2E」シナリオ🛒💳📦
注文を支払うユースケースを通して、イベント配信まで確認します🙂
PayOrder(ユースケース)を呼ぶ📣Order.MarkAsPaid()が状態変更&OrderPaidを溜める🔔- リポジトリ保存✅
- ディスパッチャが
OrderPaidHandlerを呼ぶ📣➡️🎯 - ハンドラがメール送信(テストではフェイクで記録)📧✅
テストプロジェクト準備(xUnit v3)🧪✨
2026時点では .NET 10(LTS) が現行の土台として扱いやすいです🙂(LTSのサポート期間も明示されています) (Microsoft for Developers)
また、xUnit は v3 が推奨で、旧 xunit パッケージは “legacy” 扱いとして deprecate 表示になっています。(NuGet)
*.IntegrationTests.csproj 例(net10.0)🧩
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" />
<PackageReference Include="xunit.v3" Version="3.*" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.*" />
</ItemGroup>
<ItemGroup>
<!-- アプリ側プロジェクト参照(例) -->
<ProjectReference Include="..\MiniEc.Application\MiniEc.Application.csproj" />
<ProjectReference Include="..\MiniEc.Infrastructure\MiniEc.Infrastructure.csproj" />
</ItemGroup>
</Project>
- xUnit v3 は
.NET 8+をサポートしています。 (NuGet) - Visual Studio の Test Explorer で動かすためのアダプターが
xunit.runner.visualstudioで、v3 のテストも実行できます。 (NuGet)
まず大事:DI構成を“テストで使い回せる形”にする🧩🧰
統合テストは「本番と同じDI登録」を使わないと意味が薄くなりがちです🙂 なので DI登録を拡張メソッドに寄せるのが鉄板です✨
例:アプリ側にこういう登録を用意しておく🏗️
using Microsoft.Extensions.DependencyInjection;
public static class ServiceRegistration
{
public static IServiceCollection AddApplication(this IServiceCollection services)
{
// ユースケース(例)
services.AddScoped<PayOrderUseCase>();
// ドメインイベントのディスパッチャ
services.AddScoped<IDomainEventDispatcher, DomainEventDispatcher>();
// ハンドラ登録(例)
services.AddScoped<IDomainEventHandler<OrderPaid>, OrderPaidSendMailHandler>();
return services;
}
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
{
// ここでは本番用のDB/メール等を登録する想定
services.AddScoped<IOrderRepository, EfOrderRepository>();
services.AddScoped<IEmailSender, SmtpEmailSender>();
return services;
}
}
💡 統合テストでは AddInfrastructure() の一部だけ テスト用に差し替えるのがよくある形です🙂🎭
テスト用の差し替え部品(フェイク)🎭📧
「本番メール送信」は統合テストでやらないほうが安全です😇 代わりに送った記録だけ残すフェイクを使います📮✨
フェイク EmailSender(送信履歴を保持)📧📝
public sealed class FakeEmailSender : IEmailSender
{
private readonly List<(string To, string Subject, string Body)> _sent = new();
public IReadOnlyList<(string To, string Subject, string Body)> Sent => _sent;
public Task SendAsync(string to, string subject, string body, CancellationToken ct = default)
{
_sent.Add((to, subject, body));
return Task.CompletedTask;
}
}
インメモリ OrderRepository(DBの代わり)🗃️✨
public sealed class InMemoryOrderRepository : IOrderRepository
{
private readonly Dictionary<Guid, Order> _store = new();
public Task<Order?> FindAsync(Guid orderId, CancellationToken ct = default)
=> Task.FromResult(_store.TryGetValue(orderId, out var o) ? o : null);
public Task SaveAsync(Order order, CancellationToken ct = default)
{
_store[order.Id] = order;
return Task.CompletedTask;
}
// テスト用:事前投入
public void Seed(Order order) => _store[order.Id] = order;
}
統合テスト本体:DIで組んで、ユースケースを通す🧩🏁
ポイントはこれです👇🙂✨
ServiceCollectionに 本番相当の登録を入れる- 外部I/Oだけフェイクに差し替える
- ユースケースを呼んで、副作用(ハンドラ動作)まで確認する
統合テスト例(xUnit v3)🧪🔔
using Microsoft.Extensions.DependencyInjection;
using Xunit;
public sealed class DomainEventsIntegrationTests
{
[Fact]
public async Task PayOrder_Dispatches_OrderPaid_And_HandlerRuns()
{
// 1) DIを組む🧩
var services = new ServiceCollection();
services.AddApplication();
services.AddInfrastructure();
// 2) 外部I/Oをテスト用に差し替え🎭
var fakeEmail = new FakeEmailSender();
var inMemoryRepo = new InMemoryOrderRepository();
services.AddSingleton<IEmailSender>(fakeEmail);
services.AddSingleton<IOrderRepository>(inMemoryRepo);
// validateScopes: true で「スコープ不整合」も早期に落とす🧨
using var provider = services.BuildServiceProvider(validateScopes: true);
// 3) テストデータ準備🛒
var order = Order.PlaceNew(
id: Guid.NewGuid(),
customerEmail: "alice@example.com",
total: Money.Yen(1200)
);
inMemoryRepo.Seed(order);
// 4) ユースケース実行📣
var useCase = provider.GetRequiredService<PayOrderUseCase>();
await useCase.ExecuteAsync(order.Id);
// 5) 期待結果(状態)✅
var saved = await inMemoryRepo.FindAsync(order.Id);
Assert.NotNull(saved);
Assert.True(saved!.IsPaid);
// 6) 期待結果(ハンドラ副作用)📧✅
Assert.Single(fakeEmail.Sent);
Assert.Contains("支払い", fakeEmail.Sent[0].Subject);
}
}
もう1本だけ:DI登録ミスを即死させる「解決テスト」💥🧰
「イベント自体は出てるのに、ハンドラが登録されてなくて動かない…」 これ、あるあるです😵💫
そこで、代表サービスが解決できることをテストで固定します🙂✨
using Microsoft.Extensions.DependencyInjection;
using Xunit;
public sealed class DiWiringTests
{
[Fact]
public void ServiceProvider_CanResolve_KeyServices()
{
var services = new ServiceCollection();
services.AddApplication();
using var provider = services.BuildServiceProvider(validateScopes: true);
// “配線の要”を解決できるか確認🧩
Assert.NotNull(provider.GetRequiredService<PayOrderUseCase>());
Assert.NotNull(provider.GetRequiredService<IDomainEventDispatcher>());
}
}
この1本があるだけで、DI登録漏れがかなり減ります🙂🧯
よくある事故ポイント(ここを見ればだいたい直る)🧯😇
事故①:イベントは溜まってるのに、配信されない📮❌
- ユースケース側で「保存後に Dispatch」を呼び忘れ🙃
- 例:
repository.SaveAsync(order); dispatcher.Dispatch(order.DomainEvents);が抜けてる
✅ 対策:統合テストで「ハンドラの副作用」を必ず検証📧✅
事故②:ハンドラが動かない(登録漏れ)🧩❌
IDomainEventHandler<OrderPaid>をDI登録してない- 名前空間違い、プロジェクト参照漏れ…など
✅ 対策:上の「解決テスト」を置く💥🧰
事故③:SingletonがScopedを抱えて爆発💣
BuildServiceProvider(validateScopes: true)を使うと、早めに落ちてくれて助かります🙂🧨
統合テストの“数”の目安(迷ったらこれ)📏🙂
- Happy Path 1〜3本(最重要の流れだけ)🏁
- 失敗系 1本(メール失敗でも注文は成功扱い?など)💥📧
- それ以上は、単体テストに寄せて増やすほうがコスパ良いことが多いです🙂✨
(発展)DBも本物でやりたくなったら:Testcontainers 🐳🧪
「DBの方言」「EFのマッピング」まで含めて安心したい場合は、Docker上に本物DBを立てて統合テストする手もあります🙂 Testcontainers を使うと、テスト中だけコンテナを立てて、終わったら片付けられます🧹✨ (Microsoft for Developers)
AI拡張にお願いすると強いプロンプト例🤖🧠✨
- 「このDI登録(ServiceCollection)から、統合テスト用の
BuildServiceProvider(validateScopes: true)セットアップを作って」🧩 - 「
PayOrderUseCaseの統合テストを書いて。外部I/Oはフェイクにして、副作用をアサートして」🧪📧 - 「統合テストが落ちた。例外ログから“DI登録漏れ”か“スコープ不整合”か切り分けて」🔎🧯
チェックリスト✅📝
- 統合テストは「DI + ディスパッチ配線」を確認する🧩🔔
- 外部I/Oはフェイクにして、速く・安全に🏃♀️💨
-
validateScopes: trueで “地雷” を早期爆発させる🧨 - 少数の重要ケースだけに絞る🙂🎯
- ハンドラ副作用(例:メール送信の記録)まで必ず見る📧✅
次章では「取りこぼし問題(永続化が必要になる理由)」に進みます😱📦