bUnitでBlazorのパラメーターテストを極める

C#

前回のブログでは、bUnitを使ったテストの概要と3つの基本的なテスト例を紹介しました。

bUnitを活用すると、Blazorコンポーネントのさまざまな動作を網羅的にテストできます。今回は特にパラメーターに焦点を当て、さまざまなパターンのテストを実施します。

今回は以下のbUnit公式サイトの内容をもとにしています。

前提

以下の環境で動作確認を行っています。

oswindows11
.NET8.0.304
UIBlazor(WebAssembly standalone)
IDEVisual Studio 2022 Community
Bootstrap5.3.3

また、以下を前提としています:

  • Blazorプロジェクト(Bootstrap導入済み)を作成済み。
  • ソースコードは説明に必要な部分のみを抜粋。
  • ユニットテストの基礎を理解している。

テスト用サンプル画面の作成

テストの対象として、リストの内容を更新できるモーダルコンポーネントを作成しました。

このモーダルは以下の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-[パラメーター名]を使うことでコンポーネント間でデータのやり取りを簡単に行うことができます。今回は、チェックボックスの選択状態がモーダル内で変更された際に、親コンポーネントのリストにも反映されるかをテストします。

テストの流れ

  1. 初期状態のリストを作成(アメリカのチェックボックスがオフ)
  2. RenderComponentを使ってモーダルをレンダリング
  3. モーダル内のチェックボックスを取得し、値を変更checkBox.Change(true)
  4. 「設定」ボタンをクリックし、イベントを発火button.Click()
  5. 親コンポーネントのリストが更新されたか確認
	[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の @bindonchange イベントを利用していることや、テストコードで Change メソッドを使用することで値を変更できることを確認しました。

bUnitを活用することで、Blazorコンポーネントの動作を効率的に検証し、品質を向上させることができます。今回の内容が、Blazorテストの理解を深める一助となれば幸いです。

コメント