画面の挙動をアプリと同じようにテストできないか調べたところ、「bUnit」というテストライブラリを見つけました。本記事では、bUnitを試した結果を紹介します。
bUnitについて
BlazorはC#を用いたWebアプリケーションフレームワークであり、コンポーネント単位での開発が可能です。bUnitは、そのBlazorコンポーネントを単体テストするためのテストライブラリで、UIの挙動や表示内容をコードで検証できます。
bUnitを使用すると、次のようなメリットがあります。
- コンポーネントの動作を自動テストできる
- ユーザー操作(クリックや入力)をシミュレーションできる
- モックを活用し、API通信を伴うコンポーネントのテストが可能
本記事の内容は、bUnitの公式ページをもとに実施しています。詳細は以下をご覧ください。
前提
以下の環境で動作確認を行っています。
os | windows11 |
.NET | 8.0.304 |
UI | Blazor(WebAssembly standalone) |
IDE | Visual Studio 2022 Community |
Bootstrap | 5.3.3 |
テスト用のサンプル画面の作成
まず、テスト対象となるサンプル画面を作成します。以下のようなシンプルなユーザー検索画面を用意しました。

bUnitの設定
bUnitはBlazorのコンポーネントをユニットテストするためのライブラリです。セットアップとして、bUnitをプロジェクトに追加し、必要な依存関係を導入します。

画面上に表示されているテキストのテスト
サンプル画面のボタンのテキストやメッセージの表示を確認するテストを作成します。
public class BUnitTest : TestContext
{
[Fact]
public void SampleTest()
{
// Arrange
var cut = RenderComponent<Sample>();
var button = cut.Find("button");
var result = cut.Find("#result");
// Assert
Assert.Equal("検索", button.InnerHtml);
Assert.Equal("検索してください", result.InnerHtml);
}
}
テストの大まかな実施の流れは以下のようになります。
- RenderComponent<T>メソッドを使い、テスト対象のコンポーネントをレンダリング
- Findメソッドを使い、ボタンやテキスト要素を取得
- Assert.Equalメソッドで期待する表示内容と比較
Findメソッドでは、HTMLタグ、id、class名など様々な方法で対象の要素を取得できます。
パラメータを渡したテスト
サンプル画面では、メッセージをパラメータとして渡すことで初期メッセージを指定できます。テストでもパラメータを設定して動作を検証します。
[Fact]
public void ParameterTest()
{
// Arange
var cut = RenderComponent<Sample>(p => p.Add(m => m.FirstMessage, "test message"));
var result = cut.Find("#result");
// Assert
Assert.Equal("test message", result.InnerHtml);
}
テスト内容は先ほどと同じですが、レンダリング時にパラメータを渡しています。
フォームのSubmitのテスト
フォームに情報を入力してボタンをクリックすると、APIから取得したデータを表示する処理をテストします。
RichardSzalay.MockHttpの追加
サンプル画面では HttpClient を使用してAPI通信を行いますが、bUnit単体では HttpClient のモックがサポートされていません。そのため、RichardSzalay.MockHttp というライブラリを利用します。
テスト実施
[Fact]
public void MethodTest()
{
// Arrange
var mock = Services.AddMockHttpClient();
var response = new List<Person> { new Person { Name = "Test", Country = "Brazil" } };
var jsonResponse = JsonSerializer.Serialize(response);
mock.When("https://localhost:7013/users*").Respond("application/json", jsonResponse);
var cut = RenderComponent<Sample>();
var button = cut.Find("button");
// Act
button.Click();
cut.Render();
var contents = cut.FindAll(".col-2");
// Assert
Assert.NotEmpty(contents);
Assert.Equal("Test", contents[2].InnerHtml);
Assert.Equal("Brazil", contents[3].InnerHtml);
}
Arrangeの内容を簡単に説明します。DIを行うためにはServicesに対してサービスを登録します。ServicesはテストクラスにTestContextを継承することで利用できます。Program.csでのIServiceCollectionと同じように使うことができます。AddMockHttpClientは公式ページで紹介されているServicesの拡張メソッドです。
public static class MockHelper
{
public static MockHttpMessageHandler AddMockHttpClient(this TestServiceProvider services)
{
var mockHttpHandler = new MockHttpMessageHandler();
var httpClient = mockHttpHandler.ToHttpClient();
services.AddSingleton<HttpClient>(httpClient);
return mockHttpHandler;
}
}
続いて、mockのWhenとRespondメソッドを用いて、モックの動作を設定します。メソッド名の通りですが、Whenでモック動作の条件を指定して、Respondメソッドに条件が満たされたときのレスポンスの値を指定します。
その後、ボタンをクリックし、最新のレンダリング結果を取得して検証します。
まとめ
本記事では、BlazorのコンポーネントをbUnitを使ってテストする方法を紹介しました。
- bUnitを利用すると、コンポーネントのレンダリング結果やイベント処理をテストできる
- RenderComponent<T>メソッドを使用してコンポーネントを作成し、ボタンやメッセージの表示を検証
- MockHttpを利用することで、HTTP通信を行うコンポーネントのテストも可能
サンプル画面のコード
@inject ILogger<Sample> logger
@inject HttpClient httpClient
<h3>ユーザー情報</h3>
<EditForm Model="InputForm" OnSubmit="GetUsersAsync">
<div class="form-control">
<label for="countrySelect">国</label>
<InputSelect id="countrySelect" class="form-select" @bind-Value="InputForm.Country">
<option value="Japan">日本</option>
<option value="US">アメリカ</option>
<option value="Italy">イタリア</option>
<option value="Frnce">フランス</option>
</InputSelect>
</div>
<button class="btn btn-primary ms-3 mt-1">検索</button>
</EditForm>
<div id="result" class="container mt-3">
@if (People is not null && People.Any())
{
<div class="row">
<div class="col-2 border border-black">名前</div>
<div class="col-2 border border-black">国</div>
</div>
@foreach (var person in People)
{
<div class="row">
<div class="col-2 border border-black">@person.Name</div>
<div class="col-2 border border-black">@person.Country</div>
</div>
}
}
else
{
@message
}
</div>
@code {
[Parameter]
public string? FirstMessage { get; set; }
private Form InputForm { get; set; } = new();
private List<Person>? People;
private string? message;
protected override void OnInitialized()
{
message = FirstMessage ?? "検索してください";
}
private async Task GetUsersAsync()
{
logger.LogInformation($"country={InputForm.Country}");
var r = await httpClient.GetAsync($"https://localhost:7013/users?country={InputForm.Country}");
var j = await r.Content.ReadAsStringAsync();
People = JsonSerializer.Deserialize<List<Person>>(j);
if (People is null || !People.Any()) message = "該当のユーザーは存在しません";
}
private class Form
{
public string? Country { get; set; } = "Japan";
}
}
コメント