第十三戒: 試練と証明 〜 WicketTester は神の試練なり
Wicket には WicketTester という強力なテストフレームワークが組み込まれています。
ブラウザを起動せずに、Java コードだけでページのレンダリング、フォーム送信、
AJAX 操作をテストできます。
Wicket のテスト
Wicket のコンポーネント指向アーキテクチャにより、
UIコンポーネントのユニットテストが非常に書きやすくなっています。
WicketTester は以下のことが可能です:
- ページのレンダリングが成功するかの検証
- 特定のコンポーネントが存在するかの検証
- コンポーネントの表示内容の検証
- フォーム送信のシミュレーション
- リンクのクリックシミュレーション
- AJAX 操作のシミュレーション
- バリデーションエラーの検証
テスト環境のセットアップ
Wicket 10 の変更点: WicketTester は Wicket 10 で
wicket-core から独立した wicket-tester モジュールに分離されました。
テスト依存に追加する必要があります。
<dependencies>
<!-- Wicket テスト -->
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-tester</artifactId>
<version>10.8.0</version>
<scope>test</scope>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
WicketTester の基本
WicketTester は WebApplication のインスタンスを受け取って初期化します。
テストクラスの典型的な構造は以下の通りです。
import org.apache.wicket.util.tester.WicketTester;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class HomePageTest {
private WicketTester tester;
@BeforeEach
void setUp() {
tester = new WicketTester(new WicketApplication());
}
@Test
void homePageRendersSuccessfully() {
// ホームページをレンダリング
tester.startPage(HomePage.class);
// HTTP 200 が返ることを確認
tester.assertRenderedPage(HomePage.class);
// エラーメッセージがないことを確認
tester.assertNoErrorMessage();
}
}
ページレンダリングテスト
@Test
void testPageRendering() {
tester.startPage(HomePage.class);
// 正しいページがレンダリングされたか
tester.assertRenderedPage(HomePage.class);
// 特定のコンポーネントが存在するか
tester.assertComponent("message", Label.class);
// コンポーネントの表示内容を検証
tester.assertLabel("message", "Hello, Wicket!");
// コンポーネントが表示されているか
tester.assertVisible("message");
// コンポーネントが非表示か
tester.assertInvisible("hiddenPanel");
}
@Test
void testPageWithParameters() {
// PageParameters 付きでページをレンダリング
PageParameters params = new PageParameters();
params.add("id", 42);
tester.startPage(UserDetailPage.class, params);
tester.assertRenderedPage(UserDetailPage.class);
}
コンポーネントのテスト
コンポーネントのパスは、ページルートからの : 区切りのパスで指定します。
@Test
void testComponents() {
tester.startPage(UserPage.class);
// 直接の子コンポーネント
tester.assertComponent("userPanel", UserInfoPanel.class);
// ネストしたコンポーネント(パスは : 区切り)
tester.assertComponent("userPanel:name", Label.class);
// フォーム内のコンポーネント
tester.assertComponent("form:username", TextField.class);
// コンポーネントのモデル値を検証
tester.assertModelValue("userPanel:name", "田中太郎");
// HTML 出力に特定の文字列が含まれるか
tester.assertContains("田中太郎");
}
フォーム送信テスト
FormTester を使うと、フォームの入力と送信をシミュレートできます。
@Test
void testFormSubmit() {
tester.startPage(LoginPage.class);
// FormTester を取得(パスはフォームの wicket:id)
FormTester formTester = tester.newFormTester("loginForm");
// フォームに値を入力
formTester.setValue("username", "admin");
formTester.setValue("password", "secret123");
// フォームを送信
formTester.submit();
// 送信後のページを検証
tester.assertRenderedPage(DashboardPage.class);
tester.assertNoErrorMessage();
}
@Test
void testFormValidation() {
tester.startPage(LoginPage.class);
FormTester formTester = tester.newFormTester("loginForm");
// 必須フィールドを空のまま送信
formTester.setValue("username", "");
formTester.submit();
// 同じページに留まる(バリデーションエラー)
tester.assertRenderedPage(LoginPage.class);
// エラーメッセージが表示されている
tester.assertErrorMessages("'username' は必須です。");
}
フォームの各種入力
FormTester formTester = tester.newFormTester("form");
// テキストフィールド
formTester.setValue("name", "田中太郎");
// チェックボックス
formTester.setValue("agree", true);
// ドロップダウン(インデックスで選択)
formTester.select("city", 2); // 3番目の選択肢
// ラジオボタン(インデックスで選択)
formTester.select("gender", 0);
// 特定のボタンで送信
formTester.submit("saveButton");
リンクのテスト
@Test
void testLinkClick() {
tester.startPage(HomePage.class);
// リンクをクリック
tester.clickLink("aboutLink");
// 遷移先のページを検証
tester.assertRenderedPage(AboutPage.class);
}
@Test
void testBookmarkableLink() {
tester.startPage(HomePage.class);
// BookmarkablePageLink の遷移先を検証
tester.assertBookmarkablePageLink("userListLink",
UserListPage.class, new PageParameters());
}
AJAX テスト
AJAX コンポーネントのテストも WicketTester でサポートされています。
@Test
void testAjaxLink() {
tester.startPage(CounterPage.class);
// 初期値を確認
tester.assertLabel("count", "0");
// AjaxLink をクリック
tester.clickLink("increment");
// AJAX 更新後の値を確認
tester.assertLabel("count", "1");
// もう一度クリック
tester.clickLink("increment");
tester.assertLabel("count", "2");
}
@Test
void testAjaxButton() {
tester.startPage(SearchPage.class);
FormTester formTester = tester.newFormTester("searchForm");
formTester.setValue("query", "Wicket");
// AjaxButton をクリック(AJAX で送信)
tester.executeAjaxEvent("searchForm:searchBtn", "click");
// AJAX 更新後の結果を検証
tester.assertContains("Wicket");
tester.assertNoErrorMessage();
}
@Test
void testAjaxBehavior() {
tester.startPage(MyPage.class);
// AjaxEventBehavior を発火させる
tester.executeAjaxEvent("clickableBox", "click");
// 結果を検証
tester.assertComponentOnAjaxResponse("feedbackPanel");
}
tester.clickLink() は AJAX リンクと通常リンクの両方に使えます。
Wicket が自動的に判別して適切に処理します。
FeedbackPanel のテスト
@Test
void testFeedbackMessages() {
tester.startPage(RegistrationPage.class);
FormTester formTester = tester.newFormTester("regForm");
formTester.submit();
// エラーメッセージがあることを確認
tester.assertErrorMessages(new String[]{
"'name' は必須です。",
"'email' は必須です。"
});
// 情報メッセージの検証
// tester.assertInfoMessages("保存しました");
// エラーメッセージがないことを確認
// tester.assertNoErrorMessage();
// 情報メッセージがないことを確認
// tester.assertNoInfoMessage();
}
テストのベストプラクティス
テスト用の Application を用意する
テスト時には DB 接続やサービスをモック化した Application を使うと便利です。
public class TestApplication extends WicketApplication {
@Override
public void init() {
super.init();
// テスト用の設定
// モックサービスの登録など
}
}
// テストクラスで使用
@BeforeEach
void setUp() {
tester = new WicketTester(new TestApplication());
}
Panel のユニットテスト
Panel を単独でテストするには startComponentInPage() を使います。
@Test
void testPanel() {
User user = new User("田中太郎", "[email protected]");
// Panel を仮のページに配置してテスト
tester.startComponentInPage(
new UserInfoPanel("panel", Model.of(user)));
tester.assertLabel("panel:name", "田中太郎");
tester.assertLabel("panel:email", "[email protected]");
}
主要なアサーションメソッド一覧
| メソッド | 用途 |
|---|---|
assertRenderedPage(Class) | レンダリングされたページのクラスを検証 |
assertComponent(path, Class) | 指定パスのコンポーネントのクラスを検証 |
assertLabel(path, expected) | Label の表示テキストを検証 |
assertModelValue(path, expected) | コンポーネントのモデル値を検証 |
assertVisible(path) | コンポーネントが表示されていることを検証 |
assertInvisible(path) | コンポーネントが非表示であることを検証 |
assertEnabled(path) | コンポーネントが有効であることを検証 |
assertDisabled(path) | コンポーネントが無効であることを検証 |
assertContains(regexp) | HTML出力に正規表現がマッチすることを検証 |
assertErrorMessages(msgs) | エラーメッセージを検証 |
assertNoErrorMessage() | エラーメッセージがないことを検証 |
assertComponentOnAjaxResponse(path) | AJAX レスポンスにコンポーネントが含まれることを検証 |
WicketTester はサーブレットコンテナなしで動作するため、テストの実行が非常に高速です。 CI/CD パイプラインにも容易に組み込めます。
実務でハマりやすい注意点
-
問題開発環境では出ないのに、本番で期限切れページの問題が多発する。障害報告として後から顕在化しやすい。原因戻る操作、タブ複製、新規タブなどの実運用シナリオをテスト観点に入れていない。日常操作が未検証になっている。対策期限切れページを含む操作シナリオを E2E/結合テストに組み込む。再現しやすい手順をテストケースとして固定する。
-
問題Ajax テストは成功するのに、実画面では機能が壊れている。テスト結果と体感品質が一致しない。原因コンポーネント更新の確認だけで終わり、再描画後の JavaScript 再初期化を検証していない。フロント側の復旧が抜けている。対策Ajax 応答の検証に加えて、再初期化後の UI 動作も必ず確認する。画面操作ベースのテストを併用する。
-
問題セッションが切れた瞬間、ユーザーがどこにも進めなくなる。更新ボタンを押しても反応しない状態になる。原因Ajax 失敗時の遷移や再認証導線を事前にテストしていない。タイムアウト時の復帰仕様が未確認のままになっている。対策セッションタイムアウトを強制発生させるテストを作成し、ログイン画面へ復帰できることを確認する。復帰後の再操作も検証する。