| name | bunit |
| description | bUnit 2.x を使用した Blazor コンポーネントのユニットテストのベストプラクティスとガイドライン |
bUnit 2.x — Blazor コンポーネントテストガイド
bUnit 2.x (現行最新 v2.7.x) を使った Blazor コンポーネントのユニットテスト作成を支援する。
テストコードは C# (.cs) ファイルで記述し、テストフレームワークには MSTest を使用する。
重要: bUnit 2.x は .NET 8 以上 を必須とする。
詳細なリファレンスは references/ フォルダを参照 — 必要に応じてオンデマンドで読み込む。
| リファレンス | 内容 |
|---|
| parameters.md | パラメータ渡しの全パターン (EventCallback, ChildContent, Bind, Cascading 等) |
| test-doubles.md | JSInterop, NavigationManager, 認証, HttpClient, ComponentStub |
| async-testing.md | WaitForAssertion, WaitForState, WaitForElement, TaskCompletionSource |
1. プロジェクトセットアップ
<Project Sdk="MSTest.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="bunit" Version="2.7.*" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyApp\MyApp.csproj" />
</ItemGroup>
</Project>
global.json には .NET 10 の Microsoft Testing Platform runner と MSTest.Sdk のバージョンを指定する。
{
"test": {
"runner": "Microsoft.Testing.Platform"
},
"msbuild-sdks": {
"MSTest.Sdk": "4.2.1"
}
}
テストプロジェクト内に .razor ファイルを置く特別な理由がない限り、SDK は MSTest.Sdk を使用すること。アプリ側の Razor コンポーネントは Web プロジェクトへの ProjectReference 経由でテストする。
2. テストの基本構造
BunitContext を継承 (推奨)
[TestClass]
public sealed class CounterTests : BunitContext
{
[TestMethod]
public void Counter_InitialRender_DisplaysZero()
{
var cut = Render<Counter>();
cut.Find("p").MarkupMatches("<p>Current count: 0</p>");
}
}
BunitContext を都度生成
[TestMethod]
public void Counter_InitialRender_DisplaysZero()
{
using var ctx = new BunitContext();
var cut = ctx.Render<Counter>();
cut.MarkupMatches("<p>Current count: 0</p>");
}
3. コア API クイックリファレンス
レンダリング
var cut = Render<MyComponent>();
var cut = Render<MyComponent>(p => p.Add(x => x.Name, "test"));
cut.Render(p => p.Add(x => x.Name, "updated"));
マークアップ検証
cut.MarkupMatches("<p>Hello</p>");
cut.Find("h1").MarkupMatches("<h1>Title</h1>");
Assert.AreEqual("Hello", cut.Find("p").TextContent);
要素・コンポーネント検索
var btn = cut.Find("button.primary");
var items = cut.FindAll("li");
var child = cut.FindComponent<TaskItem>();
var children = cut.FindComponents<TaskItem>();
イベント
cut.Find("button").Click();
cut.Find("input").Change("new value");
cut.Find("form").Submit();
cut.Find("input").TriggerEvent("oncustompaste", new CustomPasteEventArgs { ... });
非同期待機
cut.WaitForAssertion(() => cut.Find("p").MarkupMatches("<p>Done</p>"));
cut.WaitForState(() => cut.Instance.IsLoaded);
cut.WaitForElement("button.submit");
cut.WaitForElements("li", 3);
cut.WaitForComponent<ListItem>();
サービス登録
Services.AddSingleton<IMyService>(new MockMyService());
JSInterop.Mode = JSRuntimeMode.Loose;
ComponentFactories.AddStub<HeavyChild>();
4. ベストプラクティス
構造
- テストクラスは
sealed にする
- AAA パターン (Arrange-Act-Assert)
- テスト名:
ComponentName_Scenario_ExpectedBehavior
- 変数名
cut を使用
マークアップ検証
- 再利用コンポーネント →
MarkupMatches で構造を検証
- アプリ固有コンポーネント →
Find + TextContent / Instance でセマンティックに検証
- 不要な詳細を検証しない — 脆いテストの原因になる
非同期
OnInitializedAsync を持つコンポーネントには必ず WaitForAssertion / WaitForState を使用
TaskCompletionSource<T> で非同期依存をシミュレート
サービス・テストダブル
- テスト対象の依存は 必ず
Services に登録
- JSInterop は デフォルトで Strict モード — 必要な呼び出しは事前に
Setup する
- 子コンポーネントの分離には
ComponentFactories.AddStub<T>() を使用
避けるべきこと
- ❌
FindAll の結果を再利用せずに更新を期待する(再度 FindAll を呼ぶこと)
- ❌ JSInterop の
Setup なしに JS 呼び出しを持つコンポーネントをレンダリング