前回のブログでは、bUnitを使ったテストの概要と3つの基本的なテスト例を紹介しました。
bUnitを活用すると、Blazorコンポーネントのさまざまな動作を網羅的にテストできます。今回は特にパラメーターに焦点を当て、さまざまなパターンのテストを実施します。
今回は以下のbUnit公式サイトの内容をもとにしています。
前提
以下の環境で動作確認を行っています。
os | windows11 |
.NET | 8.0.304 |
UI | Blazor(WebAssembly standalone) |
IDE | Visual Studio 2022 Community |
Bootstrap | 5.3.3 |
テスト用サンプル画面の作成
テストの対象として、リストの内容を更新できるモーダルコンポーネントを作成しました。

このモーダルは以下の4つのパラメーターを受け取ります。
- 通常のパラメーター(List):双方向バインディングされるリスト本体
- カスケーディングパラメーター(Title):モーダルを表示するボタンのテキスト
- RenderFragment(HeaderContent):モーダルのヘッダー部分のHTML
- EventCallback(ListChanged):親コンポーネントとリストを同期するためのイベント
@typeparam TItem where TItem : CheckBox, ICloneable
<button id="titleButton" type="button" class="btn btn-primary m-2" data-bs-toggle="modal" data-bs-target="#countryModal">
@Title
</button>
<div class="modal fade" id="countryModal" data-bs-backdrop="static" data-bs-keyboard="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
@HeaderContent
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="Reset"></button>
</div>
<div class="d-flex">
<div class="modal-body">
@foreach (var c in List)
{
<div class="form-check">
<input class="form-check-input" type="checkbox" id="@c.Name" @bind="c.IsChecked" />
<label class="form-check-label" for="@c.Name">@c.Displayname</label>
</div>
}
</div>
</div>
<div class="modal-footer">
<button id="setButton" type="button" class="btn btn-primary" data-bs-dismiss="modal" @onclick="SetEvent">設定</button>
</div>
</div>
</div>
</div>
@code {
[CascadingParameter]
public string? Title { get; set; }
[Parameter]
public List<TItem> List { get; set; } = default!;
[Parameter]
public RenderFragment? HeaderContent { get; set; }
[Parameter]
public EventCallback<List<TItem>> ListChanged { get; set; }
private List<TItem> BackupList { get; set; } = default!;
private async Task SetEvent() => await ListChanged.InvokeAsync(List);
protected override void OnParametersSet()
{
BackupList = List.Select(item => (TItem)item.Clone()).ToList();
}
private void Reset()
{
List = BackupList.Select(item => (TItem)item.Clone()).ToList();
}
}
親コンポーネントからは以下のように呼び出します。
<CascadingValue Value="@title">
<CustomModal TItem="Country" @bind-List="Countries">
<HeaderContent>
<h3>国を選択</h3>
</HeaderContent>
</CustomModal>
</CascadingValue>
これらのパラメーターを持ったモーダルをbUnitでテストしていきます。
各種パラメーターのテスト
通常のパラメーターのテスト
最初に、リストを渡してモーダル内で正しくレンダリングされるかを確認します。
[Fact]
public void ModalParameterTest()
{
// Arrange
var list = new List<Country>()
{
new Country {Name="Japan",Displayname="日本",IsChecked=true},
new Country {Name="US",Displayname="アメリカ",IsChecked=false}
};
var cut = RenderComponent<CustomModal<Country>>(parameters => parameters
.Add(p => p.List, list)
);
var countries = cut.FindAll(".form-check-input");
// Assert
Assert.Equal(2, countries.Count);
Assert.Equal("US", countries[1].Id);
Assert.False(countries[1].IsChecked());
}
RenderComponentでテスト対象のコンポーネントをレンダリングする際に、Addメソッドを利用してパラメータを渡します。
カスケーディングパラメーターのテスト
カスケーディングパラメーターを適切に渡せるかをテストします。カスケーディングパラメーターも普通のパラメーターと同じようにAddメソッドで渡すことができます。
[Fact]
public void ModalCascadTest()
{
// Arrange
var cut = RenderComponent<CustomModal<Country>>(parameters => parameters
.Add(p => p.Title, "Test Title")
);
var titleButton = cut.Find("#titleButton");
// Assert
Assert.Equal("Test Title", titleButton.InnerHtml);
}
RenderFragmentパラメーターのテスト
RenderFragmentを適切に受け取ってレンダリングできるかを確認します。RenderFragmentも渡し方は同じです。渡す値にhtmlを設定するだけです。
[Fact]
public void ModalRendarFragmentTest()
{
// Arrange
var cut = RenderComponent<CustomModal<Country>>(parameters => parameters
.Add(p => p.HeaderContent, "<h3>Header</h3>")
);
var header = cut.Find("h3");
// Assert
Assert.Equal("Header", header.InnerHtml);
}
双方向バインディング(イベント)のテスト
最後に、イベントを発火させて親コンポーネントのリストが正しく更新されるかを確認します。
Blazorの双方向バインディングでは、@bind-[パラメーター名]を使うことでコンポーネント間でデータのやり取りを簡単に行うことができます。今回は、チェックボックスの選択状態がモーダル内で変更された際に、親コンポーネントのリストにも反映されるかをテストします。
テストの流れ
- 初期状態のリストを作成(アメリカのチェックボックスがオフ)
- RenderComponentを使ってモーダルをレンダリング
- モーダル内のチェックボックスを取得し、値を変更(checkBox.Change(true))
- 「設定」ボタンをクリックし、イベントを発火(button.Click())
- 親コンポーネントのリストが更新されたか確認
[Fact]
public void ModalEventTest()
{
// Arrange
var list = new List<Country>()
{
new Country {Name="Japan",Displayname="日本",IsChecked=true},
new Country {Name="US",Displayname="アメリカ",IsChecked=false}
};
Action listChanged = () => { };
var resultList = new List<Country>();
var cut = RenderComponent<CustomModal<Country>>(parameters => parameters
.Bind(p => p.List, list, newList => list = newList)
);
var countries = cut.FindAll(".form-check-input");
var checkBox = countries[1];
var button = cut.Find("#setButton");
checkBox.Change(true);
button.Click();
cut.Render();
countries = cut.FindAll(".form-check-input");
checkBox = countries[1];
// Assert
Assert.Equal(2, countries.Count);
Assert.Equal("US", countries[1].Id);
Assert.True(countries[1].IsChecked());
Assert.True(list[1].IsChecked);
}
Bindメソッドで渡した変数list内のアメリカのチェックボックスがtrueに更新されます。また、再レンダリング後にチェックボックスを取得し直すことでもアメリカのチェックボックスがtrueに更新されていることを確認できます。
最後に以上の全4テストを実行した結果を載せます。

Blazorにおける@bindの仕組みとテストへの応用
Blazorでは、@bind を使用することで input 要素とフィールドやプロパティをバインドできます。これにより、UIの変更が直接データに反映され、逆にデータの変更がUIにも反映される双方向バインディングが実現されます。
@bindの動作を確認する方法
以下の設定を .csproj ファイルに追加し、プロジェクトをビルドすると、Razor ファイルから生成される C# のコードを確認できます。
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
この設定を加えてビルドした後、生成された C# ファイルを確認すると、次のように @bind=”c.IsChecked” によって onchange イベントが自動的に登録されていることが分かります。

Blazor における @bind は、内部的に onchange イベントを利用しています。そのため、テストコードでは Change メソッドを実行することで、擬似的に onchange を発火させ、値の変更を再現することができます。
この仕組みを理解することで、チェックボックスにチェックを入れるという動作をbUnitで実現できます。
まとめ
今回は、bUnitを使用してBlazorコンポーネントのさまざまなパラメーターをテストする方法を紹介しました。通常のパラメーター、カスケーディングパラメーター、RenderFragment、そして双方向バインディングを含むイベントのテスト方法を詳しく解説しました。特に双方向バインディングの仕組みを深掘りし、Blazorの @bind が onchange イベントを利用していることや、テストコードで Change メソッドを使用することで値を変更できることを確認しました。
bUnitを活用することで、Blazorコンポーネントの動作を効率的に検証し、品質を向上させることができます。今回の内容が、Blazorテストの理解を深める一助となれば幸いです。
コメント