MockMvc および WebDriver
前のセクションでは、MockMvc を生の HtmlUnit API と組み合わせて使用する方法を見てきました。このセクションでは、Selenium WebDriver (英語) 内で追加の抽象化を使用して、物事をさらに簡単にします。
なぜ WebDriver と MockMvc なのでしょうか?
すでに HtmlUnit と MockMvc を使用できますが、なぜ WebDriver を使用するのでしょうか? Selenium WebDriver は非常にエレガントな API を提供しており、コードを簡単に整理できます。それがどのように機能するかをよりよく示すために、このセクションの例を調べます。
Selenium (英語) の一部であるにもかかわらず、WebDriver はテストを実行するために Selenium Server を必要としません。 |
メッセージが適切に作成されるようにする必要があるとします。テストには、HTML フォームの入力要素の検索、入力、さまざまなアサーションの作成が含まれます。
エラー状態もテストする必要があるため、このアプローチでは多数の個別のテストが行われます。例: フォームの一部のみを入力した場合にエラーが発生するようにします。フォーム全体に入力すると、新しく作成されたメッセージが後で表示されます。
フィールドの 1 つに "summary" という名前が付けられている場合、テスト内の複数の場所で次のようなものが繰り返される可能性があります。
Java
Kotlin
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
val summaryInput = currentPage.getHtmlElementById("summary")
summaryInput.setValueAttribute(summary)
id
を smmry
に変更するとどうなるでしょうか? これを行うと、すべてのテストを更新してこの変更を組み込むように強制されます。これは DRY の原則に違反するため、次のように、理想的にはこのコードを独自のメソッドに抽出する必要があります。
Java
Kotlin
public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
setSummary(currentPage, summary);
// ...
}
public void setSummary(HtmlPage currentPage, String summary) {
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
}
fun createMessage(currentPage: HtmlPage, summary:String, text:String) :HtmlPage{
setSummary(currentPage, summary);
// ...
}
fun setSummary(currentPage:HtmlPage , summary: String) {
val summaryInput = currentPage.getHtmlElementById("summary")
summaryInput.setValueAttribute(summary)
}
そうすることで、UI を変更した場合にすべてのテストを更新する必要がなくなります。
次の例に示すように、これをさらに一歩進め、現在の HtmlPage
を表す Object
内にこのロジックを配置することもできます。
Java
Kotlin
public class CreateMessagePage {
final HtmlPage currentPage;
final HtmlTextInput summaryInput;
final HtmlSubmitInput submit;
public CreateMessagePage(HtmlPage currentPage) {
this.currentPage = currentPage;
this.summaryInput = currentPage.getHtmlElementById("summary");
this.submit = currentPage.getHtmlElementById("submit");
}
public <T> T createMessage(String summary, String text) throws Exception {
setSummary(summary);
HtmlPage result = submit.click();
boolean error = CreateMessagePage.at(result);
return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result));
}
public void setSummary(String summary) throws Exception {
summaryInput.setValueAttribute(summary);
}
public static boolean at(HtmlPage page) {
return "Create Message".equals(page.getTitleText());
}
}
class CreateMessagePage(private val currentPage: HtmlPage) {
val summaryInput: HtmlTextInput = currentPage.getHtmlElementById("summary")
val submit: HtmlSubmitInput = currentPage.getHtmlElementById("submit")
fun <T> createMessage(summary: String, text: String): T {
setSummary(summary)
val result = submit.click()
val error = at(result)
return (if (error) CreateMessagePage(result) else ViewMessagePage(result)) as T
}
fun setSummary(summary: String) {
summaryInput.setValueAttribute(summary)
}
fun at(page: HtmlPage): Boolean {
return "Create Message" == page.getTitleText()
}
}
}
以前は、このパターンはページオブジェクトパターン [GitHub] (英語) として知られていました。確かに HtmlUnit でこれを行うことができますが、WebDriver には、このパターンを実装しやすくするために次のセクションで検討するいくつかのツールが用意されています。
MockMvc および WebDriver のセットアップ
Spring MVC テストフレームワークで Selenium WebDriver を使用するには、プロジェクトに org.seleniumhq.selenium:selenium-htmlunit-driver
へのテスト依存関係が含まれていることを確認してください。
次の例に示すように、MockMvcHtmlUnitDriverBuilder
を使用して、MockMvc と統合する Selenium WebDriver を簡単に作成できます。
Java
Kotlin
WebDriver driver;
@BeforeEach
void setup(WebApplicationContext context) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build();
}
lateinit var driver: WebDriver
@BeforeEach
fun setup(context: WebApplicationContext) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build()
}
これは、MockMvcHtmlUnitDriverBuilder を使用する簡単な例です。より高度な使用箇所については、高度な MockMvcHtmlUnitDriverBuilder を参照してください。 |
上記の例では、サーバーとして localhost
を参照する URL が、実際の HTTP 接続を必要とせずに MockMvc
インスタンスにリダイレクトされるようにします。他の URL は、通常どおりネットワーク接続を使用してリクエストされます。これにより、CDN の使用を簡単にテストできます。
MockMvc および WebDriver の使用箇所
これで、通常どおり WebDriver を使用できますが、アプリケーションをサーブレットコンテナーにデプロイする必要はありません。例: 次のメッセージを作成するためにビューをリクエストできます:
Java
Kotlin
CreateMessagePage page = CreateMessagePage.to(driver);
val page = CreateMessagePage.to(driver)
次に、フォームに入力して送信し、次のようにメッセージを作成します。
Java
Kotlin
ViewMessagePage viewMessagePage =
page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
val viewMessagePage =
page.createMessage(ViewMessagePage::class, expectedSummary, expectedText)
これにより、ページオブジェクトパターンを活用することにより、HtmlUnit テストの設計が改善されます。なぜ WebDriver と MockMvc なのでしょうか? で説明したように、HtmlUnit でページオブジェクトパターンを使用できますが、WebDriver でははるかに簡単です。次の CreateMessagePage
実装を検討してください。
Java
Kotlin
public class CreateMessagePage extends AbstractPage { (1)
(2)
private WebElement summary;
private WebElement text;
@FindBy(css = "input[type=submit]") (3)
private WebElement submit;
public CreateMessagePage(WebDriver driver) {
super(driver);
}
public <T> T createMessage(Class<T> resultPage, String summary, String details) {
this.summary.sendKeys(summary);
this.text.sendKeys(details);
this.submit.click();
return PageFactory.initElements(driver, resultPage);
}
public static CreateMessagePage to(WebDriver driver) {
driver.get("http://localhost:9990/mail/messages/form");
return PageFactory.initElements(driver, CreateMessagePage.class);
}
}
1 | CreateMessagePage は AbstractPage を継承します。AbstractPage の詳細については説明しませんが、要約すると、すべてのページに共通の機能が含まれています。例: アプリケーションにナビゲーションバー、グローバルエラーメッセージ、その他の機能がある場合、このロジックを共有の場所に配置できます。 |
2 | 関心のある HTML ページの各部分にメンバー変数があります。これらは WebElement 型です。WebDriver の PageFactory [GitHub] (英語) では、各 WebElement を自動的に解決することにより、HtmlUnit バージョンの CreateMessagePage から多くのコードを削除できます。PageFactory#initElements(WebDriver,Class<T>) (英語) メソッドは、フィールド名を使用し、HTML ページ内の要素の id または name で検索することにより、各 WebElement を自動的に解決します。 |
3 | @FindBy アノテーション [GitHub] (英語) を使用して、デフォルトの検索動作をオーバーライドできます。この例では、@FindBy アノテーションを使用して、css セレクター (input[type=submit] ) で送信ボタンを検索する方法を示しています。 |
class CreateMessagePage(private val driver: WebDriver) : AbstractPage(driver) { (1)
(2)
private lateinit var summary: WebElement
private lateinit var text: WebElement
@FindBy(css = "input[type=submit]") (3)
private lateinit var submit: WebElement
fun <T> createMessage(resultPage: Class<T>, summary: String, details: String): T {
this.summary.sendKeys(summary)
text.sendKeys(details)
submit.click()
return PageFactory.initElements(driver, resultPage)
}
companion object {
fun to(driver: WebDriver): CreateMessagePage {
driver.get("http://localhost:9990/mail/messages/form")
return PageFactory.initElements(driver, CreateMessagePage::class.java)
}
}
}
1 | CreateMessagePage は AbstractPage を継承します。AbstractPage の詳細については説明しませんが、要約すると、すべてのページに共通の機能が含まれています。例: アプリケーションにナビゲーションバー、グローバルエラーメッセージ、その他の機能がある場合、このロジックを共有の場所に配置できます。 |
2 | 関心のある HTML ページの各部分にメンバー変数があります。これらは WebElement 型です。WebDriver の PageFactory [GitHub] (英語) では、各 WebElement を自動的に解決することにより、HtmlUnit バージョンの CreateMessagePage から多くのコードを削除できます。PageFactory#initElements(WebDriver,Class<T>) (英語) メソッドは、フィールド名を使用し、HTML ページ内の要素の id または name で検索することにより、各 WebElement を自動的に解決します。 |
3 | @FindBy アノテーション [GitHub] (英語) を使用して、デフォルトのルックアップ動作をオーバーライドできます。この例では、@FindBy アノテーションを使用して、css セレクター(input [type = submit])で送信ボタンを検索する方法を示します。 |
最後に、新しいメッセージが正常に作成されたことを確認できます。以下のアサーションは、AssertJ (英語) アサーションライブラリを使用します。
Java
Kotlin
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
assertThat(viewMessagePage.message).isEqualTo(expectedMessage)
assertThat(viewMessagePage.success).isEqualTo("Successfully created a new message")
ViewMessagePage
を使用すると、カスタムドメインモデルとやり取りできることがわかります。例: Message
オブジェクトを返すメソッドを公開します:
Java
Kotlin
public Message getMessage() throws ParseException {
Message message = new Message();
message.setId(getId());
message.setCreated(getCreated());
message.setSummary(getSummary());
message.setText(getText());
return message;
}
fun getMessage() = Message(getId(), getCreated(), getSummary(), getText())
その後、アサーションでリッチドメインオブジェクトを使用できます。
最後に、テストが完了したら、次のように WebDriver
インスタンスを閉じることを忘れないでください。
Java
Kotlin
@AfterEach
void destroy() {
if (driver != null) {
driver.close();
}
}
@AfterEach
fun destroy() {
if (driver != null) {
driver.close()
}
}
WebDriver の使用に関する追加情報については、Selenium WebDriver ドキュメント [GitHub] (英語) を参照してください。
高度な MockMvcHtmlUnitDriverBuilder
これまでの例では、Spring TestContext フレームワークによってロードされた WebApplicationContext
に基づいて WebDriver
を構築することにより、可能な限り最も簡単な方法で MockMvcHtmlUnitDriverBuilder
を使用しました。このアプローチは、次のようにここで繰り返されます。
Java
Kotlin
WebDriver driver;
@BeforeEach
void setup(WebApplicationContext context) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build();
}
lateinit var driver: WebDriver
@BeforeEach
fun setup(context: WebApplicationContext) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build()
}
次のように、追加の構成オプションを指定することもできます。
Java
Kotlin
WebDriver driver;
@BeforeEach
void setup() {
driver = MockMvcHtmlUnitDriverBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
}
lateinit var driver: WebDriver
@BeforeEach
fun setup() {
driver = MockMvcHtmlUnitDriverBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build()
}
別の方法として、次のように MockMvc
インスタンスを個別に構成して MockMvcHtmlUnitDriverBuilder
に提供することにより、まったく同じセットアップを実行できます。
Java
Kotlin
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
driver = MockMvcHtmlUnitDriverBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
これはより冗長ですが、MockMvc
インスタンスを使用して WebDriver
を構築することにより、指先で MockMvc の全機能を使用できます。
MockMvc インスタンスの作成に関する追加情報については、セットアップの選択を参照してください。 |