Spring Boot + npm + Geb で入力フォームを作ってテストする ( その100 )( Gradle を 6.9.1 → 7.2 へ、Spring Boot を 2.4.10 → 2.5.4 へ、Geb を 4.1 → 5.0 へバージョンアップする2 )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
- 前回からの続きです。
参照したサイト・書籍
Difference between mockito-core vs mockito-inline
https://stackoverflow.com/questions/65986197/difference-between-mockito-core-vs-mockito-inlineMocking static methods (since 3.4.0)
https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#static_mocksmvc controller test with session attribute
https://stackoverflow.com/questions/26341400/mvc-controller-test-with-session-attribute
目次
- JUnit 4 の依存関係が残っているので削除する
- gebTest タスクが成功するか確認する
- powermock を利用しているテストを powermock なしで動作するよう変更する
- mockito-inline を依存関係に追加した後にテストが失敗する原因を調査する
apply(sharedHttpSession())
を呼び出して MockMvc の request 間で session 情報が自動で共有されるようにする- @Unroll アノテーションを削除する
手順
JUnit 4 の依存関係が残っているので削除する
gradlew dependencies
コマンドを実行して、出力結果から JUnit 4(junit:junit:4
) を検索すると以下の2つのモジュールでヒットしました。
org.springframework.boot:spring-boot-starter-web
com.icegreen:greenmail
+--- org.springframework.boot:spring-boot-starter-web -> 2.5.4 | .......... | +--- org.springframework.boot:spring-boot-starter-json:2.5.4 | | +--- org.springframework.boot:spring-boot-starter:2.5.4 (*) | | +--- org.springframework:spring-web:5.3.9 | | | +--- org.springframework:spring-beans:5.3.9 (*) | | | \--- org.springframework:spring-core:5.3.9 (*) | | +--- com.fasterxml.jackson.core:jackson-databind:2.12.4 | | | +--- com.fasterxml.jackson.core:jackson-annotations:2.12.4 | | | | \--- com.fasterxml.jackson:jackson-bom:2.12.4 | | | | +--- junit:junit:4.13.1 -> 4.13.2 (c) | | | | +--- com.fasterxml.jackson.core:jackson-annotations:2.12.4 (c) | | | | +--- com.fasterxml.jackson.core:jackson-core:2.12.4 (c) | | | | +--- com.fasterxml.jackson.core:jackson-databind:2.12.4 (c) | | | | +--- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.4 (c) | | | | +--- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.4 (c) | | | | \--- com.fasterxml.jackson.module:jackson-module-parameter-names:2.12.4 (c) | | | +--- com.fasterxml.jackson.core:jackson-core:2.12.4 | | | | \--- com.fasterxml.jackson:jackson-bom:2.12.4 (*) | | | \--- com.fasterxml.jackson:jackson-bom:2.12.4 (*) | | +--- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.4 | | | +--- com.fasterxml.jackson.core:jackson-core:2.12.4 (*) | | | +--- com.fasterxml.jackson.core:jackson-databind:2.12.4 (*) | | | \--- com.fasterxml.jackson:jackson-bom:2.12.4 (*) | | +--- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.4 | | | +--- com.fasterxml.jackson.core:jackson-annotations:2.12.4 (*) | | | +--- com.fasterxml.jackson.core:jackson-core:2.12.4 (*) | | | +--- com.fasterxml.jackson.core:jackson-databind:2.12.4 (*) | | | \--- com.fasterxml.jackson:jackson-bom:2.12.4 (*) | | \--- com.fasterxml.jackson.module:jackson-module-parameter-names:2.12.4 | | +--- com.fasterxml.jackson.core:jackson-core:2.12.4 (*) | | +--- com.fasterxml.jackson.core:jackson-databind:2.12.4 (*) | | \--- com.fasterxml.jackson:jackson-bom:2.12.4 (*) | .......... .......... +--- com.icegreen:greenmail:1.6.5 | +--- com.sun.mail:jakarta.mail:1.6.7 (*) | +--- org.slf4j:slf4j-api:1.7.32 | \--- junit:junit:4.13.2
build.gradle を変更します。
dependencies { .......... // dependency-management-plugin によりバージョン番号が自動で設定されるもの // Appendix F. Dependency versions ( https://docs.spring.io/spring-boot/docs/2.1.4.RELEASE/reference/html/appendix-dependency-versions.html ) 参照 implementation("org.springframework.boot:spring-boot-starter-web") { exclude group: "junit", module: "junit" } .......... // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの .......... testImplementation("com.icegreen:greenmail:1.6.5") { exclude group: "junit", module: "junit" }
implementation("org.springframework.boot:spring-boot-starter-web")
とtestImplementation("com.icegreen:greenmail:1.6.5")
にexclude group: "junit", module: "junit"
を追加します。
Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
再度 gradlew dependencies
コマンドを実行して JUnit 4 が依存関係から削除されていることを確認します。
clean タスク実行 → Rebuild Project を実行すると、ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput02FormValidatorTest.groovy でエラーが発生しました。
エラーメッセージの箇所にジャンプすると @RunWith(Enclosed)
が使用されていました。テストが認識されていないのはこのファイルでした。
以下の点を変更します。
@RunWith(Enclosed)
を削除します。
clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると "BUILD SUCCESSFUL" のメッセージが出力されて、成功したテストの件数が 147 → 146 になりました。powermock を利用したテストを 1件コメントアウトしているので、これで全てのテストが認識されたことになります。
gebTest タスクが成功するか確認する
まず src/test/groovy/geb/gebspec/inquiry/InquiryTestSpec.groovy で MailServerResource が使用されていたので MailServerExtension に変更します。
class InquiryTestSpec extends GebSpec { MailServerExtension mailServerExtension = new MailServerExtension() Sql sql def setup() { mailServerExtension.start() // 外部プロセスから接続するので H2 TCP サーバへ接続する sql = Sql.newInstance("jdbc:h2:tcp://localhost:9092/mem:bootnpmgebdb", "sa", "") sql.execute("truncate table INQUIRY_DATA") } def cleanup() { mailServerExtension.stop() sql.close() }
- InquiryTestSpec クラスでは
@SringBootTest
アノテーションを付与していないため@Autowired
を付与しても意味がないので、@Rule
アノテーションを削除しMailServerResource mailServerResource = new MailServerResource()
→MailServerExtension mailServerExtension = new MailServerExtension()
に変更します。 - setup メソッド内に
mailServerExtension.start()
を追加します。 - cleanup メソッド内に
mailServerExtension.stop()
を追加します。 - テストクラス内の
mailServerResource.
→mailServerExtension.
に変更します。
gebTest タスクを実行すると、テストが 1件も実行されていません。。。
Geb のテストクラスに @Test
アノテーションは付与していないしテストが認識されない原因は何だろう?と思いつつ調査した結果、build.gradle に記述されている chromeTest、filrefoxTest タスクに useJUnitPlatform()
が記述されていないためでした。
build.gradle の task "${driver}Test"(type: Test) { ... }
内に useJUnitPlatform()
を追加します。
def drivers = ["chrome", "firefox"] drivers.each { driver -> task "${driver}Test"(type: Test) { // 前回実行時以降に何も更新されていなくても必ず実行する outputs.upToDateWhen { false } systemProperty "geb.env", driver exclude "ksbysample/**" // for JUnit 5 useJUnitPlatform() testLogging { afterSuite printTestCount } } } task gebTest { dependsOn drivers.collect { tasks["${it}Test"] } enabled = false }
gebTest タスクを実行すると全てのテストが実行されて成功しました。
powermock を利用しているテストを powermock なしで動作するよう変更する
src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput02FormValidatorTest.groovy に記述している powermock を利用しているテストを、
@RunWith(PowerMockRunner) @PowerMockRunnerDelegate(SpringRunner) @SpringBootTest @PrepareForTest(EmailValidator) @PowerMockIgnore(["javax.management.*", "com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "org.w3c.dom.*", "com.sun.org.apache.xalan.*"]) static class InquiryInput02FormValidator_メールアドレス { @Autowired private InquiryInput02FormValidator input02FormValidator Errors errors InquiryInput02Form inquiryInput02Form @Before void setup() { errors = TestHelper.createErrors() inquiryInput02Form = new InquiryInput02Form( zipcode1: "102" , zipcode2: "0072" , address: "東京都千代田区飯田橋1-1" , tel1: "" , tel2: "" , tel3: "" , email: "taro.tanaka@sample.co.jp") } @Test void "メールアドレスの Validation のテスト"() { when: "EmailValidator.validate が true を返すように設定してテストする" PowerMockito.mockStatic(EmailValidator) PowerMockito.when(EmailValidator.validate(Mockito.any())) thenReturn(true) input02FormValidator.validate(inquiryInput02Form, errors) then: "入力チェックエラーは発生しない" assert errors.hasErrors() == false assert errors.getAllErrors().size() == 0 // EmailValidator.validate が呼び出されていることをチェックする PowerMockito.verifyStatic(EmailValidator, Mockito.times(1)) EmailValidator.validate("taro.tanaka@sample.co.jp") and: "EmailValidator.validate が false を返すように設定してテストする" PowerMockito.when(EmailValidator.validate(Mockito.any())) thenReturn(false) input02FormValidator.validate(inquiryInput02Form, errors) then: "入力チェックエラーが発生する" assert errors.hasErrors() == true assert errors.getAllErrors().size() == 1 } }
以下のように変更します。
@Nested @SpringBootTest static class InquiryInput02FormValidator_メールアドレス { @Autowired private InquiryInput02FormValidator input02FormValidator Errors errors InquiryInput02Form inquiryInput02Form @BeforeEach void setup() { errors = TestHelper.createErrors() inquiryInput02Form = new InquiryInput02Form( zipcode1: "102" , zipcode2: "0072" , address: "東京都千代田区飯田橋1-1" , tel1: "" , tel2: "" , tel3: "" , email: "taro.tanaka@sample.co.jp") } @Test void "メールアドレスの Validation のテスト"() { when: "EmailValidator.validate が true を返すように設定してテストする" Mockito.mockStatic(EmailValidator) Mockito.when(EmailValidator.validate(Mockito.any())).thenReturn(true) input02FormValidator.validate(inquiryInput02Form, errors) then: "入力チェックエラーは発生しない" assert errors.hasErrors() == false assert errors.getAllErrors().size() == 0 // EmailValidator.validate が呼び出されていることをチェックする Mockito.verify(EmailValidator, Mockito.times(1)) EmailValidator.validate("taro.tanaka@sample.co.jp") and: "EmailValidator.validate が false を返すように設定してテストする" Mockito.when(EmailValidator.validate(Mockito.any())) thenReturn(false) input02FormValidator.validate(inquiryInput02Form, errors) then: "入力チェックエラーが発生する" assert errors.hasErrors() == true assert errors.getAllErrors().size() == 1 } }
Mockito で static method のテストを実装するには mockito-inline が必要なので、build.gradle の dependencies block に testImplementation("org.mockito:mockito-inline")
を追加します。
dependencies { .......... testImplementation("org.codehaus.groovy:groovy-sql") testImplementation("org.mockito:mockito-inline")
Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
テスト単体で実行すると成功しました。
clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると、test タスクで今回変更したテスト以外のテストがなぜか失敗しました。。。
mockito-inline を依存関係に追加した後にテストが失敗する原因を調査する
エラーの原因を調べようとテストを IntelliJ IDEA から単体で実行してみると成功します。
mockito-inline を依存関係に追加したのが原因だろうと思い、依存関係から外して powermock を利用しているテストを powermock なしで動作するよう変更する で変更したテストもコメントアウトして build タスクを実行すると、テストは全て成功します。
mockito-inline を依存関係に追加し、コメントアウトしたテストも元に戻します。
コンソールに出力されているエラーメッセージだけでは詳細が分からないので build/reports/tests/test/index.html のレポートファイルを開いてみると、失敗した手テスト全てで Caused by: java.lang.IllegalArgumentException: セットされるはずのデータがセットされていません
のログが出力されていました。
上のログから該当箇所を確認してみると、mockito-inline が依存関係に追加されると mockMvc.perform(TestHelper.postForm("/inquiry/input/02?move=next", inquiryInput02Form_001).with(csrf()).session(session))
でデータを渡せなくなる(TestHelper.postForm メソッドでデータをセットできない?)ように見えるのですが、これだけでは解決の仕方が分かりません。
Web でいろいろ検索して調べたところ、以下の2つのページを見つけました。
Mockito.mockStatic メソッドを使用する場合には try-with-resources 構文を使うように書かれていますね。。。 まさか、Mockito.mockStatic 戻り値を変数に取っておいて close メソッドを呼び出す必要があるとは思いませんでした。
powermock を利用しているテストを powermock なしで動作するよう変更する で変更したテストを以下のように変更します。
@Test void "メールアドレスの Validation のテスト"() { given: MockedStatic mockedEmailValidator = Mockito.mockStatic(EmailValidator) when: "EmailValidator.validate が true を返すように設定してテストする" Mockito.when(EmailValidator.validate(Mockito.any())).thenReturn(true) input02FormValidator.validate(inquiryInput02Form, errors) then: "入力チェックエラーは発生しない" assert errors.hasErrors() == false assert errors.getAllErrors().size() == 0 // EmailValidator.validate が呼び出されていることをチェックする Mockito.verify(EmailValidator, Mockito.times(1)) EmailValidator.validate("taro.tanaka@sample.co.jp") and: "EmailValidator.validate が false を返すように設定してテストする" Mockito.when(EmailValidator.validate(Mockito.any())) thenReturn(false) input02FormValidator.validate(inquiryInput02Form, errors) then: "入力チェックエラーが発生する" assert errors.hasErrors() == true assert errors.getAllErrors().size() == 1 cleanup: mockedEmailValidator.close() }
- given block を追加して Mockito.mockStatic を呼び出している行をこの下に移動し、
Mockito.mockStatic(EmailValidator)
→MockedStatic mockedEmailValidator = Mockito.mockStatic(EmailValidator)
に変更します。 - cleanup block を追加し、
mockedEmailValidator.close()
を追加します。
clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると "BUILD SUCCESSFUL" のメッセージが出力されました。
apply(sharedHttpSession())
を呼び出して MockMvc の request 間で session 情報が自動で共有されるようにする
mockito-inline を依存関係に追加した後にテストが失敗する原因を調査する の調査をしていた時に mvc controller test with session attribute のページを見つけました。MockMvc のインスタンスを生成する時に apply(sharedHttpSession())
を呼び出せば session 情報が自動で共有されるようになるとのこと。
以下のような実装の場合、
@BeforeEach void setup() { mockMvc = MockMvcBuilders.webAppContextSetup(context) .apply(springSecurity()) .build() } @Test void "完了画面で「入力画面へ」ボタンをクリックして入力画面1へ戻ると入力していたデータがクリアされる"() { when: "入力画面1で項目全てに入力して「次へ」ボタンをクリックする" MvcResult result = mockMvc.perform(TestHelper.postForm("/inquiry/input/01?move=next", inquiryInput01Form_001).with(csrf())) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/inquiry/input/02")) .andReturn() MockHttpSession session = result.getRequest().getSession() and: "入力画面2で項目全てに入力して「次へ」ボタンをクリックする" mockMvc.perform(TestHelper.postForm("/inquiry/input/02?move=next", inquiryInput02Form_001).with(csrf()).session(session)) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/inquiry/input/03")) .......... }
以下のように変更します。
@BeforeEach void setup() { mockMvc = MockMvcBuilders.webAppContextSetup(context) .apply(springSecurity()) .apply(sharedHttpSession()) .build() } @Test void "完了画面で「入力画面へ」ボタンをクリックして入力画面1へ戻ると入力していたデータがクリアされる"() { when: "入力画面1で項目全てに入力して「次へ」ボタンをクリックする" mockMvc.perform(TestHelper.postForm("/inquiry/input/01?move=next", inquiryInput01Form_001).with(csrf())) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/inquiry/input/02")) and: "入力画面2で項目全てに入力して「次へ」ボタンをクリックする" mockMvc.perform(TestHelper.postForm("/inquiry/input/02?move=next", inquiryInput02Form_001).with(csrf())) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/inquiry/input/03")) .......... }
- MockMvcBuilders クラスで MockMvc のインスタンスを生成している処理に
.apply(sharedHttpSession())
を追加します。 - テストメソッド内で
MvcResult result =
と.andReturn()
を削除します。 MockHttpSession session = result.getRequest().getSession()
も削除します。- その下の
.session(session)
の部分を全て削除します。
clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると "BUILD SUCCESSFUL" のメッセージが出力されました。
ちなみに @AutoConfigureMockMvc アノテーションを付与して MockMvc のインスタンスを自動生成した場合、session 情報は共有されませんでした。apply(sharedHttpSession())
による session 情報共有の機能を利用したい場合には自分でインスタンスを生成する必要があります。
@Unroll アノテーションを削除する
Spock 2 から @Unroll アノテーションの記述が不要になったので、削除します。
削除後に clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると "BUILD SUCCESSFUL" のメッセージが出力されて、テスト数も削除前と同じ 147 でした。
履歴
2021/10/12
初版発行。