第49章:Blazor + DI(依存の差し替えをUIでも活かす)🔁
まず、今日の“最新メモ”だけ置いとくね📝
- .NET 10 の最新パッチは **10.0.2(2026/01/13公開)**だよ📌 (Microsoft)
- BlazorのDIは、コンポーネントにサービスを注入して使う仕組みで、コンストラクタ注入 /
@injectどっちもOKだよ🙆♀️ (Microsoft Learn) - bUnitでは、描画前に
TestContext.Servicesにサービス登録すれば、そのまま差し替えテストできるよ🧪 (bUnit) - 同じインターフェースを複数回
AddSingletonした場合、**単体解決では後勝ち(上書き)**になるよ(IEnumerable<T>で全部も取れる)🔁 (Microsoft Learn)
1) この章でできるようになること🎯✨

- Blazorコンポーネントが呼ぶサービスを DI経由にする💉
- UIテスト(bUnit)で 本物サービスをスタブに差し替える🧸
- “ネットワークやDBに繋がないUIテスト” を作れる⚡️(速い・安定・怖くない!)
2) なんでDI差し替えが大事なの?🤔💡
Blazorの画面って、だいたい裏でこういうことしてるよね👇
- API呼ぶ🌐
- DBから取る🗄️
- ログ出す🧾
- 通知する📣
でもUIテストでそれを本当にやると…
- 遅い🐢
- たまに落ちる😵💫
- 環境に依存して“再現できない”💥
だから、UIテストでは 「画面は本物、裏のサービスは偽物」 にするのが最強だよ💪✨
3) 例題:推し活グッズ検索画面🎀🔎(サービス差し替え前提)
登場人物
IGoodsQueryService:検索して一覧を返す(本番はAPI/DBでもOK)GoodsSearch.razor:入力→検索→結果表示するコンポーネント
4) まず“テストから”書く(Red)🚦🔴
bUnitで「検索ボタン押したら、結果が表示される」を先に固定しよう🧪✨ (この時点ではサービス実装はまだ無くてOK!)
using Bunit;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
public class GoodsSearchTests : TestContext
{
[Fact]
public void SearchButton_Click_ShowsResults()
{
// Arrange: サービスを“偽物”で登録(ここが第49章の主役!)🔁
Services.AddSingleton<IGoodsQueryService>(new FakeGoodsQueryService(
new[]
{
new GoodsDto("アクスタA"),
new GoodsDto("缶バッジB"),
}));
// Act: コンポーネント描画
var cut = RenderComponent<GoodsSearch>();
// 入力して検索クリック
cut.Find("input[data-testid='keyword']").Change("あ");
cut.Find("button[data-testid='search']").Click();
// Assert: 結果が表示される
var items = cut.FindAll("li[data-testid='result-item']");
Assert.Equal(2, items.Count);
}
}
ポイント💡
- bUnitは 描画前に
Services(IServiceCollection)へ登録すると、そのまま注入してくれるよ🧪 (bUnit) - ここで本物のAPIやDBに繋がないのが“勝ち筋”⚡️
5) 次に“最小の画面”を書く(Green)🚦🟢
5-1) DTOとサービスIF
public record GoodsDto(string Name);
public interface IGoodsQueryService
{
Task<IReadOnlyList<GoodsDto>> SearchAsync(string keyword);
}
5-2) コンポーネント(GoodsSearch.razor)
@inject でDI注入して、ボタンで呼ぶだけだよ😊
(Blazorは @inject / コンストラクタ注入の両方をサポートしてるよ) (Microsoft Learn)
@using System
@inject IGoodsQueryService GoodsQuery
<div>
<input data-testid="keyword" @bind="Keyword" />
<button data-testid="search" @onclick="SearchAsync">検索</button>
</div>
@if (Results is not null)
{
<ul>
@foreach (var item in Results)
{
<li data-testid="result-item">@item.Name</li>
}
</ul>
}
@code {
private string Keyword { get; set; } = "";
private IReadOnlyList<GoodsDto>? Results;
private async Task SearchAsync()
{
Results = await GoodsQuery.SearchAsync(Keyword);
}
}
6) 偽サービス(スタブ)を用意する🧸✨
テスト用の“決め打ち”サービスだよ。いちばん分かりやすい👍
public sealed class FakeGoodsQueryService : IGoodsQueryService
{
private readonly IReadOnlyList<GoodsDto> _results;
public FakeGoodsQueryService(IEnumerable<GoodsDto> results)
=> _results = results.ToList();
public Task<IReadOnlyList<GoodsDto>> SearchAsync(string keyword)
=> Task.FromResult(_results);
}
これで、さっきのテストが通るはず✅🎉
7) “呼ばれたか”も確認したい(スパイでOK)🕵️♀️✅
モックライブラリなしでもいけるよ〜!
public sealed class SpyGoodsQueryService : IGoodsQueryService
{
public string? LastKeyword { get; private set; }
private readonly IReadOnlyList<GoodsDto> _results;
public SpyGoodsQueryService(IReadOnlyList<GoodsDto> results) => _results = results;
public Task<IReadOnlyList<GoodsDto>> SearchAsync(string keyword)
{
LastKeyword = keyword;
return Task.FromResult(_results);
}
}
テストでこう書ける👇
[Fact]
public void Search_PassesKeywordToService()
{
var spy = new SpyGoodsQueryService(new[] { new GoodsDto("Tシャツ") });
Services.AddSingleton<IGoodsQueryService>(spy);
var cut = RenderComponent<GoodsSearch>();
cut.Find("input[data-testid='keyword']").Change("T");
cut.Find("button[data-testid='search']").Click();
Assert.Equal("T", spy.LastKeyword);
}
8) “差し替え”のコツ3つ🔁🧠
コツ①:登録は“描画前”にやる🧪
bUnitは 描画前に Services へ登録が基本だよ✨ (bUnit)
コツ②:同じIFを登録し直せば上書きできる🔁
AddSingleton<IGoodsQueryService>(...) をもう一回やると、単体解決では後勝ちになるよ(上書きできる)🧷 (Microsoft Learn)
コツ③:ライフタイムは“ざっくり意味”だけ掴めばOK😊
- Singleton:1個を使い回し
- Scoped:スコープごとに1個
- Transient:毎回新しい (この3つの定義自体は公式の列挙にも書いてあるよ📚) (Microsoft Learn)
9) ミニ課題(手を動かすパート)🎀✍️
課題A:空入力のとき「入力してね」を表示する😶➡️💬
- テスト:空欄で検索クリック → メッセージが出る
- 実装:空ならサービス呼ばずにメッセージ表示
課題B:0件のとき「見つからなかった」を表示する🔎💨
- テスト:偽サービスが空リスト返す → 文言表示
- 実装:
Results.Count == 0の表示分岐追加
課題C:サービス例外のとき「通信失敗」を表示する💥📡
- テスト:偽サービスが例外投げる → エラー表示
- 実装:
try/catchでUI状態に反映
10) AIの使いどころ(この章向け)🤖✨
コピペで使えるやつ置いとくね👇
- 「この画面仕様の UIテスト観点 を、正常/異常/境界値で箇条書きにして」
- 「このサービスの スタブ実装 を“最小”で作って(戻り値固定でOK)」
- 「bUnitで、入力→クリック→表示確認の テスト雛形 を出して」
採用ルールはシンプルに:テストが通る+意図に合うものだけ採用でOK👍🧪
まとめ🎉🧪
- UIテストは 画面は本物、裏の依存は 偽物に差し替え が最強🔁
- bUnitは
Servicesに登録してから描画でDI差し替えできるよ🧪 (bUnit) - 同じIFを登録し直す“上書き”も、公式ドキュメントにある挙動だよ📚 (Microsoft Learn)
この次(第50章)で、Blazorアプリ全体として「重要導線だけUIテスト」+「中心ロジックはユニットでガチガチ」を完成形にしていけるよ〜🎓🎀✨