かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

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 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その99 )( Gradle を 6.9.1 → 7.2 へ、Spring Boot を 2.4.10 → 2.5.4 へ、Geb を 4.1 → 5.0 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 前回からの続きです。

参照したサイト・書籍

  1. Difference between mockito-core vs mockito-inline
    https://stackoverflow.com/questions/65986197/difference-between-mockito-core-vs-mockito-inline

  2. Mocking static methods (since 3.4.0)
    https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#static_mocks

  3. mvc controller test with session attribute
    https://stackoverflow.com/questions/26341400/mvc-controller-test-with-session-attribute

目次

  1. JUnit 4 の依存関係が残っているので削除する
  2. gebTest タスクが成功するか確認する
  3. powermock を利用しているテストを powermock なしで動作するよう変更する
  4. mockito-inline を依存関係に追加した後にテストが失敗する原因を調査する
  5. apply(sharedHttpSession()) を呼び出して MockMvc の request 間で session 情報が自動で共有されるようにする
  6. @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 でエラーが発生しました。

f:id:ksby:20211010230408p:plain

エラーメッセージの箇所にジャンプすると @RunWith(Enclosed) が使用されていました。テストが認識されていないのはこのファイルでした。

f:id:ksby:20211010230605p:plain

以下の点を変更します。

  • @RunWith(Enclosed) を削除します。

clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると "BUILD SUCCESSFUL" のメッセージが出力されて、成功したテストの件数が 147 → 146 になりました。powermock を利用したテストを 1件コメントアウトしているので、これで全てのテストが認識されたことになります。

f:id:ksby:20211010231529p:plain

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件も実行されていません。。。

f:id:ksby:20211010233832p:plain

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 タスクを実行すると全てのテストが実行されて成功しました。

f:id:ksby:20211010235424p:plain

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」ボタンをクリックして更新します。

テスト単体で実行すると成功しました。

f:id:ksby:20211011002454p:plain

clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると、test タスクで今回変更したテスト以外のテストがなぜか失敗しました。。。

f:id:ksby:20211011003133p:plain

mockito-inline を依存関係に追加した後にテストが失敗する原因を調査する

エラーの原因を調べようとテストを IntelliJ IDEA から単体で実行してみると成功します。

f:id:ksby:20211011160108p:plain

mockito-inline を依存関係に追加したのが原因だろうと思い、依存関係から外して powermock を利用しているテストを powermock なしで動作するよう変更する で変更したテストもコメントアウトして build タスクを実行すると、テストは全て成功します。

f:id:ksby:20211011161040p:plain

mockito-inline を依存関係に追加し、コメントアウトしたテストも元に戻します。

コンソールに出力されているエラーメッセージだけでは詳細が分からないので build/reports/tests/test/index.html のレポートファイルを開いてみると、失敗した手テスト全てで Caused by: java.lang.IllegalArgumentException: セットされるはずのデータがセットされていません のログが出力されていました。

f:id:ksby:20211011214003p:plain

上のログから該当箇所を確認してみると、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" のメッセージが出力されました。

f:id:ksby:20211012091902p:plain

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" のメッセージが出力されました。

f:id:ksby:20211012095031p:plain

ちなみに @AutoConfigureMockMvc アノテーションを付与して MockMvc のインスタンスを自動生成した場合、session 情報は共有されませんでした。apply(sharedHttpSession()) による session 情報共有の機能を利用したい場合には自分でインスタンスを生成する必要があります。

@Unroll アノテーションを削除する

Spock 2 から @Unroll アノテーションの記述が不要になったので、削除します。

削除後に clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると "BUILD SUCCESSFUL" のメッセージが出力されて、テスト数も削除前と同じ 147 でした。

f:id:ksby:20211012100409p:plain

履歴

2021/10/12
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その99 )( Gradle を 6.9.1 → 7.2 へ、Spring Boot を 2.4.10 → 2.5.4 へ、Geb を 4.1 → 5.0 へバージョンアップする )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その98 )( Gradle を 6.4 → 6.9.1 へ、Spring Boot を 2.2.7 → 2.4.10 へ、Geb を 3.4 → 4.1 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Gradle を 6.9.1 → 7.2 へバージョンアップします。
    • Spring Boot を 2.4.10 → 2.5.4 へ、Geb を 4.1 → 5.0 へバージョンアップします。
    • 今回だけでは完了しなかったので、2回に分けます。

参照したサイト・書籍

  1. erdi / webdriver-binaries-gradle-plugin
    https://github.com/erdi/webdriver-binaries-gradle-plugin

目次

  1. Gradle を 6.9.1 → 7.2 へバージョンアップする
  2. Spring Boot を 2.4.10 → 2.5.4 へ、Geb を 4.1 → 5.0 へバージョンアップする。。。がエラーが出てバージョンアップできず
  3. configureChromeDriverBinary タスクでエラーになる原因を調査した結果、Gradle Plugin を com.energizedwork.webdriver-binaries → com.github.erdi.webdriver-binaries に切り替える
  4. test タスクで実行されるテストから src/test/groovy/geb/ の下のテストを除外する
  5. org.junit.Test アノテーションが付いているテストを org.junit.jupiter.api.Test アノテーションに変更する
  6. 続く。。。

手順

Gradle を 6.9.1 → 7.2 へバージョンアップする

build.gradle の wrapper タスクの記述を以下のように変更します。

wrapper {
    gradleVersion = "7.2"
    distributionType = Wrapper.DistributionType.ALL
}
  • gradleVersion = "6.9.1"gradleVersion = "7.2" に変更します。

コマンドプロンプトから gradlew wrapper --gradle-version=7.2gradlew --version コマンドを実行します。gradlew --stop で Gradle Deamon を止めただけでは java.lang.IllegalArgumentException: Unsupported class file major version 61 のエラーメッセージが出たので、PC を再起動してからコマンドを実行しています。

f:id:ksby:20211010092309p:plain

gradle/wrapper/gradle-wrapper.properties は以下の内容になります。

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Spring Boot 2.4.x の Web アプリを 2.5.x へバージョンアップする ( その4 )( Gradle を 6.9.1 → 7.2 へバージョンアップする ) で実施した変更内容を build.gradle に反映します。

tasks.named("compileTestJava").configure {
    options.errorprone.enabled = false
}
bootJar {
    duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
jar {
    enabled = false
}

// for Doma 2
// Copy the resources referred by the Doma annotation processors to
// the destinationDir of the compileJava task
task copyDomaResources(type: Sync) {
    from sourceSets.main.resources.srcDirs
    into compileJava.destinationDirectory
    include "doma.compile.config"
    include "META-INF/**/*.sql"
    include "META-INF/**/*.script"
}
compileJava.dependsOn copyDomaResources
  • bootJar { duplicatesStrategy = DuplicatesStrategy.INCLUDE } を追加します。
  • jar { enabled = false } を追加します。
  • copyDomaResources タスクの以下の点を変更します。
    • into compileJava.destinationDirinto compileJava.destinationDirectory

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。。。が、Could not find method testCompile() のエラーメッセージが表示されました。

f:id:ksby:20211010093406p:plain

古い文法のままでした。。。 その下の testRuntime も testRuntimeOnly に変更されているので、build.gradle を以下のように変更します。

dependencies {
    ..........

    // for JUnit 5
    // junit-jupiter で junit-jupiter-api, junit-jupiter-params, junit-jupiter-engine の3つが依存関係に追加される
    testImplementation("org.junit.jupiter:junit-jupiter")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
  • testCompile("org.junit.jupiter:junit-jupiter")testImplementation("org.junit.jupiter:junit-jupiter") に変更します。
  • testRuntime("org.junit.platform:junit-platform-launcher")testRuntimeOnly("org.junit.platform:junit-platform-launcher") に変更します。

再度 Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新すると、今度は問題なく終了しました。

Spring Boot を 2.4.10 → 2.5.4 へ、Geb を 4.1 → 5.0 へバージョンアップする。。。がエラーが出てバージョンアップできず

build.gradle の以下の点を変更します。

buildscript {
    ext {
        group "ksbysample"
        version "2.5.4"
    }
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
    dependencies {
        // for doma-codegen-plugin
        classpath "com.h2database:h2:1.4.200"
    }
}

plugins {
    id "java"
    id "groovy"
    id "eclipse"
    id "idea"
    id "org.springframework.boot" version "2.5.4"
    id "io.spring.dependency-management" version "1.0.11.RELEASE"
    id "net.ltgt.errorprone" version "1.1.1"
    id "checkstyle"
    id "com.github.spotbugs" version "4.0.8"
    id "pmd"
    id "com.github.node-gradle.node" version "3.1.1"
    id "com.gorylenko.gradle-git-properties" version "2.3.1"
    id "com.energizedwork.webdriver-binaries" version "1.4"
    id "org.seasar.doma.codegen" version "1.4.1"
}

..........

dependencyManagement {
    imports {
        // mavenBom は以下の URL のものを使用する
        // https://repo.spring.io/release/org/springframework/boot/spring-boot-starter-parent/2.1.4.RELEASE/
        // bomProperty に指定可能な property は以下の URL の BOM に記述がある
        // https://repo.spring.io/release/org/springframework/boot/spring-boot-dependencies/2.1.4.RELEASE/spring-boot-dependencies-2.1.4.RELEASE.pom
        mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
        mavenBom("org.junit:junit-bom:5.8.1")
    }
}

dependencies {
    def spockVersion = "2.0-groovy-3.0"
    def lombokVersion = "1.18.20"
    def domaVersion = "2.49.0"
    def errorproneVersion = "2.3.4"
    def seleniumVersion = "3.141.59"
    def spotbugsVersion = "4.0.2"

    // 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")
    implementation("org.springframework.boot:spring-boot-starter-validation")
    implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-freemarker")
    implementation("org.springframework.boot:spring-boot-starter-mail")
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    runtimeOnly("org.springframework.boot:spring-boot-devtools")
    implementation("org.springframework.session:spring-session-core")
    implementation("org.springframework.session:spring-session-jdbc")
    implementation("org.codehaus.janino:janino")
    testImplementation("org.springframework.boot:spring-boot-starter-test") {
        exclude group: "org.junit.vintage", module: "junit-vintage-engine"
    }
    testImplementation("org.springframework.security:spring-security-test")
    testImplementation("org.yaml:snakeyaml")
    testImplementation("org.codehaus.groovy:groovy-sql")

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    implementation("com.integralblue:log4jdbc-spring-boot-starter:2.0.0")
    implementation("org.flywaydb:flyway-core:7.14.1")
    implementation("com.h2database:h2:1.4.200")
    implementation("com.github.rozidan:modelmapper-spring-boot-starter:2.3.1")
    implementation("com.google.guava:guava:31.0.1-jre")
    implementation("org.apache.commons:commons-lang3:3.12.0")
    testImplementation("org.dbunit:dbunit:2.7.2")
    testImplementation("org.assertj:assertj-core:3.21.0")
    testImplementation("org.spockframework:spock-core:${spockVersion}")
    testImplementation("org.spockframework:spock-spring:${spockVersion}")
    testImplementation("org.jsoup:jsoup:1.14.3")
    testImplementation("com.icegreen:greenmail:1.6.5")

    // for lombok
    annotationProcessor("org.projectlombok:lombok:${lombokVersion}")
    compileOnly("org.projectlombok:lombok:${lombokVersion}")

    // for Doma
    implementation("org.seasar.doma:doma-core:${domaVersion}")
    annotationProcessor("org.seasar.doma:doma-processor:${domaVersion}")

    // for Error Prone ( http://errorprone.info/ )
    errorprone("com.google.errorprone:error_prone_core:${errorproneVersion}")
    compileOnly("com.google.errorprone:error_prone_annotations:${errorproneVersion}")

    // PowerMock
    testImplementation("org.powermock:powermock-module-junit4:${powermockVersion}")
    testImplementation("org.powermock:powermock-api-mockito2:${powermockVersion}")

    // for Geb + Spock
    testImplementation("org.gebish:geb-spock:5.0")
    testImplementation("org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-firefox-driver:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-support:${seleniumVersion}")

    // for SpotBugs
    compileOnly("com.github.spotbugs:spotbugs:${spotbugsVersion}")
    compileOnly("net.jcip:jcip-annotations:1.0")
    compileOnly("com.github.spotbugs:spotbugs-annotations:${spotbugsVersion}")
    testImplementation("com.google.code.findbugs:jsr305:3.0.2")
    spotbugsStylesheets("com.github.spotbugs:spotbugs:${spotbugsVersion}")
    spotbugsPlugins("com.h3xstream.findsecbugs:findsecbugs-plugin:1.10.1")

    // for JUnit 5
    // junit-jupiter で junit-jupiter-api, junit-jupiter-params, junit-jupiter-engine の3つが依存関係に追加される
    testImplementation("org.junit.jupiter:junit-jupiter")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

..........

bootRun {
    jvmArgs = jvmArgsForTask +
            [
                    "-Dspring.profiles.active=develop",
                    "-XX:TieredStopAtLevel=1"
            ]
}

tasks.withType(Test) {
    jvmArgs = jvmArgsForTask +
            ["-Dspring.profiles.active=unittest"]
}
test {
    // test タスクの jvmArgs は tasks.withType(Test) { ... } で定義している

    // for JUnit 5
    useJUnitPlatform()

    testLogging {
        afterSuite printTestCount
    }
}

..........

Spring Boot 2.5.4 へのバージョンアップとして以下の点を変更します。

  • buildscript block の以下の点を変更します。
    • version "2.4.10"version "2.5.4"
  • plugins block の以下の点を変更します。
    • id "org.springframework.boot" version "2.4.10"id "org.springframework.boot" version "2.5.4"
  • dependencyManagement block の以下の点を変更します。
    • mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) { bomProperty "groovy.version", "2.5.15" }mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)

各種ライブラリのバージョンアップとして以下の点を変更します。

  • dependencyManagement block の以下の点を変更します。
    • mavenBom("org.junit:junit-bom:5.7.2")mavenBom("org.junit:junit-bom:5.8.1")
  • dependencies block の以下の点を変更します。
    • def spockVersion = "1.3-groovy-2.5"def spockVersion = "2.0-groovy-3.0"
    • testImplementation("org.codehaus.groovy:groovy-sql") を追加します。
    • testImplementation("org.gebish:geb-spock:4.1") { exclude group: "org.codehaus.groovy", module: "groovy-all" }testImplementation("org.gebish:geb-spock:5.0")
    • https://gebish.org/manual/current/#installation-usage を見ると selenium-apiselenium-remote-driver の2つは必要ないようなので、以下の2行を削除します。
      • testImplementation("org.seleniumhq.selenium:selenium-api:${seleniumVersion}")
      • testImplementation("org.seleniumhq.selenium:selenium-remote-driver:${seleniumVersion}")
    • powermock を依存関係から削除するため、以下の3行を削除します。
      • def powermockVersion = "2.0.7"
      • testImplementation("org.powermock:powermock-module-junit4:${powermockVersion}")
      • testImplementation("org.powermock:powermock-api-mockito2:${powermockVersion}")
  • def jvmArgsAddOpens = [ ... ] を削除します。
  • テストが全て JUnit 5 ベースになるので、testJUnit4AndSpock タスクを削除します。
    • task testJUnit4AndSpock(type: Test) { ... }
    • test.dependsOn testJUnit4AndSpock

ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput02FormValidatorTest.groovy に powermock を利用したテストが記述されていますが、このままでは build が通らないので対象のテストをコメントアウトします(後で実装し直します)。

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると、configureChromeDriverBinary タスクでエラーになりました。。。

f:id:ksby:20211010184036p:plain

configureChromeDriverBinary タスクでエラーになる原因を調査した結果、Gradle Plugin を com.energizedwork.webdriver-binaries → com.github.erdi.webdriver-binaries に切り替える

gradlew configureChromeDriverBinary --stacktrace コマンドを実行してみると Caused by: java.lang.NoSuchMethodError: 'void org.gradle.wrapper.PathAssembler.<init>(java.io.File)' のエラーメッセージが出力されましたが、これでは原因がよく分かりません。

f:id:ksby:20211010184417p:plain

com.energizedwork.webdriver-binariesGoogle で検索したところ、erdi / webdriver-binaries-gradle-plugin のページにたどり着きました。新しい Gradle Plugin が出ているようなので、こちらに切り替えます。

plugins {
    ..........
    id "com.github.erdi.webdriver-binaries" version "2.6"
    ..........
}

..........

webdriverBinaries {
    chromedriver {
        version = "94.0.4606.41"
        architecture = "X86"
        fallbackTo32Bit = true
    }
    geckodriver {
        version = "0.29.1"
        architecture = "X86_64"
    }
}

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると、configureChromeDriverBinary タスクは正常終了しましたが test タスクでエラーが発生しました。

f:id:ksby:20211010190535p:plain

test タスクで実行されるテストから src/test/groovy/geb/ の下のテストを除外する

エラーになったテスト 13個は全て src/test/groovy/geb/gebspec/inquiry/InquiryTestSpec.groovy のテストでした。Spock、Geb がバージョンアップされたことに伴い JUnit 5 ベースのテストに変わったので、test タスクの実行対象になったことが原因です。Geb のテストを test タスクで実行する必要はないので除外します。

build.gradle の test タスクに exclude "geb/**" を追加します。

test {
    // test タスクの jvmArgs は tasks.withType(Test) { ... } で定義している

    // Geb のテストを test タスクで実行されないようにする
    exclude "geb/**"

    // for JUnit 5
    useJUnitPlatform()

    testLogging {
        afterSuite printTestCount
    }
}

また src/test/groovy/geb/gebspec/inquiry/InquiryTestSpec.groovy を見て気づいたのですが、JUnit 4 のクラスである org.junit.Rule の色がグレーになっていませんでした。build.gradle に記述されているモジュールの依存関係にまだ JUnit 4 が残っているようなので、後で JUnit 4 を依存関係から取り除くようにします。

f:id:ksby:20211010191414p:plain

clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると "BUILD SUCCESSFUL" のメッセージが出力されましたが、成功したテストの件数が 147 → 116 と大きく減っていました。おそらく org.junit.Test アノテーションが付いているテストがあって、JUnit 5 から認識されないことが原因でしょう。

f:id:ksby:20211010192113p:plain

org.junit.Test アノテーションが付いているテストを org.junit.jupiter.api.Test アノテーションに変更する

org.junit.Test で検索すると以下の2つのファイルがヒットしました。というより Spock ベースで作成していないテストが全て JUnit 4 ベースで実装されていました。。。

  • src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryCompleteControllerTest.groovy
  • src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputControllerTest.groovy

まず GreenMail の起動・停止に使用している src/test/java/ksbysample/common/test/rule/mail/MailServerResource.javaJUnit 4 の org.junit.rules.ExternalResource を継承して作成されているので、JUnit 5 でも動作するよう変更します。

src/test/java/ksbysample/common/test の下に extension.mail パッケージを作成した後、MailServerExtension.java を新規作成して以下の内容を記述します。最初 BeforeEachCallback, AfterEachCallback インターフェースを実装して自動で起動・停止させようと思ったのですが、Spock では @RegisterExtension を付与しても自動起動・停止しなかったので止めました。

package ksbysample.common.test.extension.mail;

import com.icegreen.greenmail.util.GreenMail;
import com.icegreen.greenmail.util.ServerSetup;
import org.springframework.stereotype.Component;

import javax.mail.internet.MimeMessage;
import java.util.Arrays;
import java.util.List;

@Component
public class MailServerExtension {

    private GreenMail greenMail = new GreenMail(new ServerSetup(25, "localhost", ServerSetup.PROTOCOL_SMTP));

    public void start() {
        greenMail.start();
    }

    public void stop() {
        greenMail.stop();
    }

    public int getMessagesCount() {
        return greenMail.getReceivedMessages().length;
    }

    public List<MimeMessage> getMessages() {
        return Arrays.asList(greenMail.getReceivedMessages());
    }

    public MimeMessage getFirstMessage() {
        MimeMessage message = null;
        MimeMessage[] receivedMessages = greenMail.getReceivedMessages();
        if (receivedMessages.length > 0) {
            message = receivedMessages[0];
        }
        return message;
    }

}

src/test/java/ksbysample/common/test/rule/mail/MailServerResource.java は削除します。

次に JUnit 4 ベースで作成されている2つのファイルを以下の内容で変更します。

  • import org.junit.Testimport org.junit.jupiter.api.Test に変更します。
  • @RunWith(Enclosed) を削除します。
  • @RunWith(SpringRunner) を削除します。
  • クラスの中にテストクラスを記述して階層構造にしている場合には、中のクラスに @Nested アノテーションを付与します。
  • MailServerResource を MailServerExtension を使用するよう変更します。GreenMail のメールサーバの起動・停止は @BeforeEach@AfterEach アノテーションが付与されたメソッド内で行います。
    @Rule
    public MailServerResource mailServerResource = new MailServerResource()

   ↓  

    @Autowired
    private MailServerExtension mailServerExtension

    @BeforeEach
    void setup() {
        mailServerExtension.start()
        ..........
    }

    @AfterEach
    void cleanup() {
        mailServerExtension.stop()
    }
  • @Before@BeforeEach に変更します。

Spock ベースで実装している src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryConfirmControllerTest.groovy も MailServerResource を使用していたので、以下の点を変更します。

  • @RunWith(Enclosed) を削除します。
  • MailServerResource を MailServerExtension を使用するよう変更します。GreenMail のメールサーバの起動・停止は setup メソッド、cleanup メソッド内で行います。
        @Rule
        MailServerResource mailServerResource = new MailServerResource()

   ↓  

        @Autowired
        private MailServerExtension mailServerExtension

        def setup() {
            mailServerExtension.start()
            ..........
        }

        def cleanup() {
            mailServerExtension.stop()
            ..........
        }
  • テストメソッド内の mailServerResource.mailServerExtension. に変更します。

clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると "BUILD SUCCESSFUL" のメッセージが出力されましたが、成功したテストの件数が 147 → 131 になりました。まだテストメソッドが認識されないものが残っているようです。

f:id:ksby:20211010205913p:plain

続く。。。

長くなったので、次回へ続きます。以下の内容を行う予定です。

  • JUnit 4 の依存関係が残っているので削除する。
  • テスト件数が減っている原因を調査・解消する。
  • gebTest タスクが成功するか確認する。成功しない場合には、その原因を調査・解消する。
  • powermock で実装されたテストを powermock なしで動作するよう変更する。

履歴

2021/10/10
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その98 )( Gradle を 6.4 → 6.9.1 へ、Spring Boot を 2.2.7 → 2.4.10 へ、Geb を 3.4 → 4.1 へバージョンアップする )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その97 )( webpack を 4.43.0 → 5.56.0 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Gradle を 6.4 → 6.9.1 へバージョンアップします。
    • Spring Boot を 2.2.7 → 2.4.10 へ、Geb を 3.4 → 4.1 へバージョンアップします。

参照したサイト・書籍

  1. Spring Boot 2.4.2 and Thymeleaf 3.0.12 - access static methods
    https://stackoverflow.com/questions/66048129/spring-boot-2-4-2-and-thymeleaf-3-0-12-access-static-methods/66053568

  2. Improve restricted expression evaluation mode
    https://github.com/thymeleaf/thymeleaf/issues/809

目次

  1. バージョンアップの進め方を決める
  2. Gradle を 6.4 → 6.9.1 へバージョンアップする
  3. Spring Boot を 2.2.7 → 2.4.10 へ、Geb を 3.4 → 4.1 へバージョンアップする

手順

バージョンアップの進め方を決める

以下の手順で進めます。

  1. JDK 11 のまま、Gradle を 6系最新へ、Spring Boot を 2.2.7 → 2.4.10 へ、Geb を 3.4 → 4.1 へバージョンアップする(Groovy は 2系のまま)。
  2. JDK 11 のまま、Gradle を 7系最新へ、Spring Boot を 2.4.10 → 2.5.4 へ、Geb を 4.1 → 5.0 へバージョンアップする(Groovy を 3系へ)。
  3. JDK 17 へ、Spring Boot を 2.5.4 → 2.5.5 へバージョンアップする。

Gradle を 6.4 → 6.9.1 へバージョンアップする

build.gradle の wrapper タスクの記述を以下のように変更します。

wrapper {
    gradleVersion = "6.9.1"
    distributionType = Wrapper.DistributionType.ALL
}
  • gradleVersion = "6.4"gradleVersion = "6.9.1" に変更します。

コマンドプロンプトから gradlew wrapper --gradle-version=6.9.1 コマンドを実行します。。。が、java.lang.IllegalArgumentException: Unsupported class file major version 61 のエラーが出力されてバージョンアップできませんでした。

f:id:ksby:20211009004629p:plain

いろいろ試してみたところ、どうも 6.4 の Gradle Deamon が起動している時に gradlew wrapper --gradle-version=6.9.1 コマンドを実行すると、このエラーが出るような気がします。PC を再起動して Gradle Daemon が何も実行されていない状態にしてみます(後で知りましたが gradlew --stop コマンドを実行すれば Gradle Daemon は停止できました)。

PC 再起動後、コマンドプロンプトから gradlew wrapper --gradle-version=6.9.1gradlew --version コマンドを実行すると今度は正常に終了しました。

f:id:ksby:20211009011624p:plain

gradle/wrapper/gradle-wrapper.properties は以下の内容になります。

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると "BUILD SUCCESSFUL" のメッセージが出力されました。

f:id:ksby:20211009012929p:plain

Spring Boot を 2.2.7 → 2.4.10 へ、Geb を 3.4 → 4.1 へバージョンアップする

build.gradle の以下の点を変更します。

buildscript {
    ext {
        group "ksbysample"
        version "2.4.10"
    }
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
    dependencies {
        // for doma-codegen-plugin
        classpath "com.h2database:h2:1.4.200"
    }
}

plugins {
    id "java"
    id "eclipse"
    id "idea"
    id "org.springframework.boot" version "2.4.10"
    id "io.spring.dependency-management" version "1.0.11.RELEASE"
    id "groovy"
    id "net.ltgt.errorprone" version "1.1.1"
    id "checkstyle"
    id "com.github.spotbugs" version "4.0.8"
    id "pmd"
    id "com.github.node-gradle.node" version "3.1.1"
    id "com.gorylenko.gradle-git-properties" version "2.3.1"
    id "com.energizedwork.webdriver-binaries" version "1.4"
    id "org.seasar.doma.codegen" version "1.4.1"
}

..........

dependencyManagement {
    imports {
        // mavenBom は以下の URL のものを使用する
        // https://repo.spring.io/release/org/springframework/boot/spring-boot-starter-parent/2.1.4.RELEASE/
        // bomProperty に指定可能な property は以下の URL の BOM に記述がある
        // https://repo.spring.io/release/org/springframework/boot/spring-boot-dependencies/2.1.4.RELEASE/spring-boot-dependencies-2.1.4.RELEASE.pom
        mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
        mavenBom("org.junit:junit-bom:5.7.2")
    }
}

dependencies {
    def spockVersion = "1.3-groovy-2.5"
    def domaVersion = "2.49.0"
    def lombokVersion = "1.18.22"
    def errorproneVersion = "2.3.4"
    def powermockVersion = "2.0.7"
    def seleniumVersion = "3.141.59"
    def spotbugsVersion = "4.0.2"

    // 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")
    implementation("org.springframework.boot:spring-boot-starter-validation")
    implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-freemarker")
    implementation("org.springframework.boot:spring-boot-starter-mail")
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    runtimeOnly("org.springframework.boot:spring-boot-devtools")
    implementation("org.springframework.session:spring-session-core")
    implementation("org.springframework.session:spring-session-jdbc")
    implementation("org.codehaus.janino:janino")
    testImplementation("org.springframework.boot:spring-boot-starter-test") {
        exclude group: "org.junit.vintage", module: "junit-vintage-engine"
    }
    testImplementation("org.springframework.security:spring-security-test")
    testImplementation("org.yaml:snakeyaml")

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    implementation("com.integralblue:log4jdbc-spring-boot-starter:2.0.0")
    implementation("org.flywaydb:flyway-core:5.2.4")
    implementation("com.h2database:h2:1.4.200")
    implementation("com.github.rozidan:modelmapper-spring-boot-starter:2.3.1")
    implementation("com.google.guava:guava:27.1-jre")
    implementation("org.apache.commons:commons-lang3:3.8.1")
    testImplementation("org.dbunit:dbunit:2.7.0")
    testImplementation("org.assertj:assertj-core:3.16.0")
    testImplementation("org.spockframework:spock-core:${spockVersion}")
    testImplementation("org.spockframework:spock-spring:${spockVersion}")
    testImplementation("org.jsoup:jsoup:1.13.1")
    testImplementation("com.icegreen:greenmail:1.5.13")

    // for lombok
    annotationProcessor("org.projectlombok:lombok:${lombokVersion}")
    compileOnly("org.projectlombok:lombok:${lombokVersion}")

    // for Doma
    implementation("org.seasar.doma:doma-core:${domaVersion}")
    annotationProcessor("org.seasar.doma:doma-processor:${domaVersion}")

    // for Error Prone ( http://errorprone.info/ )
    errorprone("com.google.errorprone:error_prone_core:${errorproneVersion}")
    compileOnly("com.google.errorprone:error_prone_annotations:${errorproneVersion}")

    // PowerMock
    testImplementation("org.powermock:powermock-module-junit4:${powermockVersion}")
    testImplementation("org.powermock:powermock-api-mockito2:${powermockVersion}")

    // for Geb + Spock
    testImplementation("org.gebish:geb-spock:3.4") {
        exclude group: "org.codehaus.groovy", module: "groovy-all"
    }
    testImplementation("org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-firefox-driver:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-support:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-api:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-remote-driver:${seleniumVersion}")

    // for SpotBugs
    compileOnly("com.github.spotbugs:spotbugs:${spotbugsVersion}")
    compileOnly("net.jcip:jcip-annotations:1.0")
    compileOnly("com.github.spotbugs:spotbugs-annotations:${spotbugsVersion}")
    testImplementation("com.google.code.findbugs:jsr305:3.0.2")
    spotbugsStylesheets("com.github.spotbugs:spotbugs:${spotbugsVersion}")
    spotbugsPlugins("com.h3xstream.findsecbugs:findsecbugs-plugin:1.10.1")

    // for JUnit 5
    // junit-jupiter で junit-jupiter-api, junit-jupiter-params, junit-jupiter-engine の3つが依存関係に追加される
    testCompile("org.junit.jupiter:junit-jupiter")
    testRuntime("org.junit.platform:junit-platform-launcher")
}

..........

Spring Boot 2.4.10 へのバージョンアップとして以下の点を変更します。

  • buildscript block の以下の点を変更します。
    • version "2.2.7-RELEASE"version "2.4.10"
  • plugins block の以下の点を変更します。
    • id "org.springframework.boot" version "2.2.7.RELEASE"id "org.springframework.boot" version "2.4.10"
    • id "io.spring.dependency-management" version "1.0.9.RELEASE"id "io.spring.dependency-management" version "1.0.11.RELEASE"
  • dependencies block の以下の点を変更します。
    • implementation("org.springframework.boot:spring-boot-starter-validation") を追加します。

各種ライブラリのバージョンアップとして以下の点を変更します。

  • plugins block の以下の点を変更します。
    • id "com.gorylenko.gradle-git-properties" version "2.2.2"id "com.gorylenko.gradle-git-properties" version "2.3.1"
    • id "org.seasar.doma.codegen" version "0.0.2"id "org.seasar.doma.codegen" version "1.4.1"
  • dependencyManagement block の以下の点を変更します。
    • mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATESmavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) { bomProperty "groovy.version", "2.5.15" }
    • mavenBom("org.junit:junit-bom:5.6.2")mavenBom("org.junit:junit-bom:5.7.2")
  • dependencies block の以下の点を変更します。
    • def lombokVersion = "1.18.12"def lombokVersion = "1.18.20"
    • def domaVersion = "2.34.0"def domaVersion = "2.49.0"
    • implementation("org.flywaydb:flyway-core:5.2.4")implementation("org.flywaydb:flyway-core:7.14.1")
    • implementation("com.google.guava:guava:27.1-jre")implementation("com.google.guava:guava:31.0.1-jre")
    • implementation("org.apache.commons:commons-lang3:3.8.1")implementation("org.apache.commons:commons-lang3:3.12.0")
    • testImplementation("org.dbunit:dbunit:2.7.0")testImplementation("org.dbunit:dbunit:2.7.2")
    • testImplementation("org.assertj:assertj-core:3.16.0")testImplementation("org.assertj:assertj-core:3.21.0")
    • testImplementation("org.jsoup:jsoup:1.13.1")testImplementation("org.jsoup:jsoup:1.14.3")
    • testImplementation("com.icegreen:greenmail:1.5.13")testImplementation("com.icegreen:greenmail:1.6.5")
    • testImplementation("org.gebish:geb-spock:3.4") {testImplementation("org.gebish:geb-spock:4.1") {

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると compileJava タスクで警告が1件、testJUnit4AndSpock タスクでテストが大量に失敗しました。

f:id:ksby:20211009141211p:plain

compileJava タスクの警告は error-prone が build/generated の下のソースをチェックして出力していました。自動生成されたコードをチェックしないよう build.gradle に disableWarningsInGeneratedCode = true の設定を追加します。

[compileJava, compileTestGroovy, compileTestJava]*.options*.encoding = "UTF-8"
[compileJava, compileTestGroovy, compileTestJava]*.options*.compilerArgs = ["-Xlint:all,-options,-processing,-path"]
tasks.withType(JavaCompile).configureEach {
    options.errorprone {
        disableWarningsInGeneratedCode = true
    }
}
tasks.named("compileTestJava").configure {
    options.errorprone.enabled = false
}

testJUnit4AndSpock タスクでテストが大量に失敗している方は、失敗したテストを 1件だけ IntelliJ IDEA 上から実行してみると Caused by: org.flywaydb.core.api.FlywayException: Found non-empty schema(s) "PUBLIC" but no schema history table. Use baseline() or set baselineOnMigrate to true to initialize the schema history table. というエラーメッセージが出力されていました。

f:id:ksby:20211009165748p:plain

Flyway が実行される時に PUBLIC スキーマが空でなく、spring.flyway.baseline-on-migrate=true を設定すれば schema history table を初期化してくれるとのことですが、spring.flyway.baseline-on-migrate=true を設定するのではなく PUBLIC テーブルが空でない原因を調べることにします。

application.properties を見ると以下の設定が記述されており、明らかにこれですね。。。 おそらくバージョンアップ前は Flywasy が先に実行されて Sprig Session のテーブル作成はその後に実行されていたのが、バージョンアップ後はその順序が逆になったものと思われます。

spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=embedded
spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/schema-h2.sql
spring.session.jdbc.table-name=SPRING_SESSION

spring.session.jdbc.initialize-schema=embeddedspring.session.jdbc.initialize-schema=never に変更して Spring Session のテーブルを自動作成しないように変更してから、

spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=never
spring.session.jdbc.table-name=SPRING_SESSION

失敗していたテスト(このテストは Spring Session の機能を利用していない)を実行してみると、成功するようになりました。

f:id:ksby:20211009171400p:plain

spring.session.jdbc.initialize-schema=never の設定はそのままにして、Flyway で Spring Session のテーブルを作成するようにします。

src/main/resources/db/migration の下に V1.2__create_spring_session_schema.sql を新規作成した後、org/springframework/session/jdbc/schema-h2.sql の内容をコピーします。

-- copy from org/springframework/session/jdbc/schema-h2.sql
CREATE TABLE SPRING_SESSION
(
    PRIMARY_ID            CHAR(36) NOT NULL,
    SESSION_ID            CHAR(36) NOT NULL,
    CREATION_TIME         BIGINT   NOT NULL,
    LAST_ACCESS_TIME      BIGINT   NOT NULL,
    MAX_INACTIVE_INTERVAL INT      NOT NULL,
    EXPIRY_TIME           BIGINT   NOT NULL,
    PRINCIPAL_NAME        VARCHAR(100),
    CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
);

CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);

CREATE TABLE SPRING_SESSION_ATTRIBUTES
(
    SESSION_PRIMARY_ID CHAR(36)      NOT NULL,
    ATTRIBUTE_NAME     VARCHAR(200)  NOT NULL,
    ATTRIBUTE_BYTES    LONGVARBINARY NOT NULL,
    CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
    CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION (PRIMARY_ID) ON DELETE CASCADE
);

再度 clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると、テストがまだ 2件失敗します。

f:id:ksby:20211009200156p:plain

原因を調査すると、改行コードを <br/> に変換している th:utext="*{inquiry} ? ${#strings.replace(#strings.escapeXml(confirmForm.inquiry), T(java.lang.System).getProperty('line.separator'), '&lt;br /&gt;')} : ''" のところで org.attoparser.ParseException: Instantiation of new objects and access to static classes is forbidden in this context というエラーメッセージが出力されていました。

              <tr>
                <th nowrap>お問い合わせの内容</th>
                <td id="inquiry"
                    th:utext="*{inquiry} ? ${#strings.replace(#strings.escapeXml(confirmForm.inquiry), T(java.lang.System).getProperty('line.separator'), '&lt;br /&gt;')} : ''">
                  ここに、<br/>
                  入力されたお問い合わせの内容が表示されます。
                </td>
              </tr>

Web で調べると Spring Boot 2.4.2 and Thymeleaf 3.0.12 - access static methods を見つけました。Thymeleaf 3.0.12 からセキュリティ強化のために T(identifier) in SpringEL の形式では呼び出せなくなったとのこと。

System.getProperty を呼び出す Bean を作成して、Thymeleaf テンプレートから呼び出すように変更します。

まず src/main/java/ksbysample/webapp/bootnpmgeb/helper の下に thymeleaf パッケージを作成し、その下に SystemPropertiesHelper.java を新規作成して以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.helper.thymeleaf;

import org.springframework.boot.system.SystemProperties;
import org.springframework.stereotype.Component;

/**
 * System Property 値取得用 Helper クラス
 */
@Component("sph")
public class SystemPropertiesHelper {

    /**
     * System#getProperty を呼び出して指定された system property の値を取得する
     *
     * @param properties 値を取得する system property 名
     * @return 取得した system property の値
     */
    public String getProperty(String... properties) {
        return SystemProperties.get(properties);
    }

}

src/main/resources/templates/web/inquiry/confirm.html を以下のように変更します。

                            <tr>
                                <th nowrap>お問い合わせの内容</th>
                                <td id="inquiry"
                                    th:utext="*{inquiry} ? ${#strings.replace(#strings.escapeXml(confirmForm.inquiry), @sph.getProperty('line.separator'), '&lt;br /&gt;')} : ''">
                                    ここに、<br/>
                                    入力されたお問い合わせの内容が表示されます。
                                </td>
                            </tr>
  • T(java.lang.System).getProperty('line.separator')@sph.getProperty('line.separator') に変更します。

再度 clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると、今度は "BUILD SUCCESSFUL" のメッセージが出力されました。

f:id:ksby:20211009203616p:plain

gebTest タスクも "BUILD SUCCESSFUL" のメッセージが表示されました。Geb のバージョンアップは特に問題は発生しないようです。

f:id:ksby:20211009205545p:plain

履歴

2021/10/10
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その97 )( webpack を 4.43.0 → 5.56.0 へバージョンアップする )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その96 )( postcss を 7.0.29 → 8.3.8 へ、postcss-cli を 7.1.1 → 9.0.1 へ、prettier を 2.0.5 → 2.4.1 へ、stylelint を 13.3.3 → 13.13.1 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • webpack を 4.43.0 → 5.56.0 へ、webpack-cli を 3.3.11 → 4.8.0 へバージョンアップします。

参照したサイト・書籍

  1. To v5 from v4
    https://webpack.js.org/migrate/5/

  2. TerserWebpackPlugin
    https://webpack.js.org/plugins/terser-webpack-plugin/

  3. webpack-contrib / terser-webpack-plugin
    https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions

  4. terser / terser
    https://github.com/terser/terser

  5. webpack@5の主な変更点まとめ
    https://blog.hiroppy.me/entry/webpack5

  6. webpack 5 にアップグレードしました
    https://medium.com/studist-dev/migration-webpack-from-4-to-5-3df31da8e7a2

  7. browserslist / browserslist
    https://github.com/browserslist/browserslist

  8. Cache
    https://webpack.js.org/configuration/cache/#cachebuilddependencies

目次

  1. webpack を 4.43.0 → 4.46.0 へ、webpack-cli を 3.3.11 → 4.8.0 へバージョンアップする
  2. webpack を 4.46.0 → 5.56.0 へバージョンアップする
  3. uglifyjs-webpack-plugin を terser-webpack-plugin に変更する
  4. package.json に browserslist を追加する
  5. cache の設定を追加する
  6. 最後に各種コマンドや build で問題がないか確認する

手順

webpack を 4.43.0 → 4.46.0 へ、webpack-cli を 3.3.11 → 4.8.0 へバージョンアップする

To v5 from v4Upgrade webpack 4 and its plugins/loaders に webpack 4、webpack-cli を最新バージョンに上げるよう記述されているので、以下のコマンドを実行します。

  • npm install --save-dev webpack@4.46.0
  • npm install --save-dev webpack-cli@4.8.0

webpack を 4.46.0 → 5.56.0 へバージョンアップする

Make sure your build has no errors or warnings に記載されている node --trace-deprecation node_modules/webpack/bin/webpack.js を実行します。

mode は起動時オプションで指定するようにしているので、まずは node --trace-deprecation node_modules/webpack/bin/webpack.js --mode production を実行してみると特に警告やエラーのメッセージは出力されませんでした。

f:id:ksby:20211005070126p:plain

次に node --trace-deprecation node_modules/webpack/bin/webpack.js --mode development を実行してみますが、こちらもメッセージは何も出力されませんでした。

f:id:ksby:20211005070328p:plain

Test webpack 5 compatibility に記載されているオプションを webpack.config.js に追加してから、

module.exports = (env, argv) => {
  return {
    node: {
      Buffer: false,
      process: false,
    },
    entry: {
      "js/inquiry/input01": ["./src/main/assets/js/inquiry/input01.js"],
      ..........

node --trace-deprecation node_modules/webpack/bin/webpack.js --mode production コマンドを実行してみましたが、メッセージ等は表示されませんでした。webpack.config.js に追加したオプションは削除します。

f:id:ksby:20211005070813p:plain

問題ないようなので、以下のコマンドを実行し webpack を 5 の最新バージョンにバージョンアップします。

  • npm install --save-dev webpack@5.56.0

f:id:ksby:20211005071338p:plain

使用していない prettier-webpack-plugin に関するメッセージが出力されているので、以下のコマンドを実行して削除します。

  • npm uninstall --save-dev prettier-webpack-plugin

uglifyjs-webpack-plugin は後で削除するので、今はこのままにします。

npm run build コマンドを実行すると webpack 5 に対応していない uglifyjs-webpack-plugin の設定を残している影響でエラーメッセージが大量に出力されます。

f:id:ksby:20211005071817p:plain f:id:ksby:20211005071857p:plain

uglifyjs-webpack-plugin を terser-webpack-plugin に変更する

以下のコマンドを実行して uglifyjs-webpack-plugin をアンインストール、terser-webpack-plugin をインストールします。

  • npm uninstall --save-dev uglifyjs-webpack-plugin
  • npm install --save-dev terser-webpack-plugin@5.2.4

terser-webpack-plugin は uglifyjs-webpack-plugin とほぼ同じオプションが指定できるそうなので webpack.config.js の optimization を以下の内容に変更します。

const webpack = require("webpack");
const TerserPlugin = require("terser-webpack-plugin");

// --mode オプションで指定された文字列を参照したい場合には argv.mode を参照する
module.exports = (env, argv) => {
  return {
    ..........
    optimization: {
      minimizer: [
        new TerserPlugin({
          terserOptions: {
            ecma: 5,
            output: {
              comments: false,
            },
            compress: {
              dead_code: true,
              drop_console: true,
            },
          },
        }),
      ],
    },
    ..........
    devtool: argv.mode === "production" ? false : "inline-source-map",
  };
};
  • const UglifyJsPlugin = require("uglifyjs-webpack-plugin");const TerserPlugin = require("terser-webpack-plugin"); に変更します。
  • new UglifyJsPlugin({new TerserPlugin({ に変更します。
  • uglifyOptions: {terserOptions: { に変更します。
  • compress オプションの指定が compress: truecompress: { ... } の2つ書かれていたことに気づいたので、compress: true の方を削除します。
  • source map を生成するか否かは devtools の設定に依存するようになったので、sourceMap: false を削除します。
  • production モードの場合には source map を生成しないよう devtool: "inline-source-map"devtool: argv.mode === "production" ? false : "inline-source-map", に変更します。

‘npm run build‘ コマンドを実行するとエラーメッセージは出なくなりました。

f:id:ksby:20211007003054p:plain

生成された js ファイルを見ると、~.LICENSE.txt というファイルも生成されるようになりました。

f:id:ksby:20211008051553p:plain

package.json に browserslist を追加する

Need to support an older browser like IE 11? を読むと package.json に browserslist を記述しておくと webpack 5 がコードスタイルを決めてくれるようになったそうです。https://github.com/postcss/autoprefixer を見ると autoprefixer も browserslist の設定を見てくれるようなので、package.json の最後に "browserslist": [ ... ] を追加します。

  ..........
  "browserslist": [
    "last 1 version",
    "> 1%",
    "IE 11"
  ]
}

postcss.config.js も以下のように変更します。

module.exports = {
  plugins: [
    require("stylelint"),
    require("autoprefixer"),
    require("cssnano")({
      preset: "default",
    }),
  ],
};
  • require("autoprefixer")({ browserlist: ["last 2 versions"] }),require("autoprefixer"), に変更します。

browserslist を追加する前は npx browserslist コマンドを実行すると以下のように出力されますが、

f:id:ksby:20211007082905p:plain

追加した後は以下のよう変わりました。

f:id:ksby:20211007083052p:plain

cache の設定を追加する

webpack 5 からファイルシステムによるキャッシュが導入されて大幅に速度改善できるようになったとのことなので、設定を追加します。

webpack.config.js に cache を追加します。development 環境では有効、production 環境では無効にします。

    output: {
      ..........
    },
    cache:
      argv.mode === "production"
        ? false
        : {
            type: "filesystem",
            buildDependencies: {
              config: [__filename],
            },
          },
    resolve: {
      ..........

最後に各種コマンドや build で問題がないか確認する

最後にコマンド、build で問題がないか一通り確認します。

npm test コマンドは問題ありません。

f:id:ksby:20211008053949p:plain

npm run build コマンドも問題ありません。

f:id:ksby:20211008054102p:plain

clean タスク実行 → Rebuild Project 実行 → build タスク実行も問題ありません。

f:id:ksby:20211008054816p:plain

gebTest タスクも問題ありません。

f:id:ksby:20211008060025p:plain

履歴

2021/10/08
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その96 )( postcss を 7.0.29 → 8.3.8 へ、postcss-cli を 7.1.1 → 9.0.1 へ、prettier を 2.0.5 → 2.4.1 へ、stylelint を 13.3.3 → 13.13.1 へバージョンアップする )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その95 )( eslint を 6.8.0 → 7.32.0 へ、jest を 26.0.1 → 27.2.4 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • autoprefixer を 9.7.6 → 10.3.6 へ、cssnano を 4.1.10 → 5.0.8 へ、postcss を 7.0.29 → 8.3.8 へ、postcss-cli を 7.1.1 → 9.0.1 へバージョンアップします。
    • prettier を 2.0.5 → 2.4.1 へバージョンアップします。
    • stylelint を 13.3.3 → 13.13.1 へ、stylelint-config-standard を 20.0.0 → 22.0.0 へバージョンアップします。

参照したサイト・書籍

目次

  1. autoprefixer を 9.7.6 → 10.3.6 へ、cssnano を 4.1.10 → 5.0.8 へ、postcss を 7.0.29 → 8.3.8 へ、postcss-cli を 7.1.1 → 9.0.1 へバージョンアップする
  2. prettier を 2.0.5 → 2.4.1 へバージョンアップする
  3. stylelint を 13.3.3 → 13.13.1 へ、stylelint-config-standard を 20.0.0 → 22.0.0 へバージョンアップする
  4. 最後に各種コマンドや build で問題がないか確認する

手順

autoprefixer を 9.7.6 → 10.3.6 へ、cssnano を 4.1.10 → 5.0.8 へ、postcss を 7.0.29 → 8.3.8 へ、postcss-cli を 7.1.1 → 9.0.1 へバージョンアップする

以下のコマンドを実行して最新のバージョンにします。

  • npm install --save-dev autoprefixer@10.3.6
  • npm install --save-dev cssnano@5.0.8
  • npm install --save-dev postcss-cli@9.0.1

npm test コマンドを実行するとテストは全て成功しましたが、

f:id:ksby:20211003235533p:plain

npm run build コマンドを実行すると Please install PostCSS 8 or above のメッセージが出力されました。postcss-cli をバージョンアップしただけでは postcss はバージョンアップされないようです。

f:id:ksby:20211003235834p:plain

node_modules/postcss/CHANGELOG.md を確認したところ 7.0.29 がインストールされていました。https://www.npmjs.com/package/postcss を見ると最新バージョンは 8.3.8 なので、以下のコマンドを実行します。

  • npm install --save-dev postcss@8.3.8

npm run build コマンドを実行すると今度は何の警告・エラーメッセージも出力されずに終了しました。

f:id:ksby:20211004000426p:plain

prettier を 2.0.5 → 2.4.1 へバージョンアップする

以下のコマンドを実行して最新のバージョンにします。

  • npm install --save-dev prettier@2.4.1

npm test コマンド、npm run build コマンドのどちらも何の警告・エラーメッセージも出力されずに終了しました。

今回のバージョンアップでは1ファイルだけフォーマットし直されており、改行位置が少し変更されていました。

f:id:ksby:20211004002913p:plain

stylelint を 13.3.3 → 13.13.1 へ、stylelint-config-standard を 20.0.0 → 22.0.0 へバージョンアップする

以下のコマンドを実行して最新のバージョンにします。

  • npm install --save-dev stylelint@13.13.1
  • npm install --save-dev stylelint-config-standard@22.0.0

npm test コマンド、npm run build コマンドのどちらも何の警告・エラーメッセージも出力されずに終了しました。

最後に各種コマンドや build で問題がないか確認する

最後にコマンド、build で問題がないか一通り確認します。

npm test コマンドは問題ありません。

f:id:ksby:20211004035203p:plain

npm run build コマンドも問題ありません。

f:id:ksby:20211004035303p:plain

clean タスク実行 → Rebuild Project 実行 → build タスク実行も問題ありません。

f:id:ksby:20211004035852p:plain

gebTest タスクも問題ありません。

f:id:ksby:20211004041427p:plain

IntelliJ IDEA の「Settings」ダイアログを開いて「Language & Frameworks」-「Node.js and NPM」を表示し、バージョンアップ後の状態も記載しておきます。

f:id:ksby:20211004041834p:plain f:id:ksby:20211004041859p:plain

履歴

2021/10/04
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その95 )( eslint を 6.8.0 → 7.32.0 へ、jest を 26.0.1 → 27.2.4 へバージョンアップする )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その94 )( Node.js を 12.16.3 → 14.18.0 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • eslint を 6.8.0 → 7.32.0 へ、jest を 26.0.1 → 27.2.4 へバージョンアップします。

参照したサイト・書籍

  1. package.json に記載されているパッケージのバージョンアップ方法 【 npm-check-updates, outdated 】
    https://qiita.com/sugurutakahashi12345/items/df736ddaf65c244e1b4f

  2. Testing Asynchronous Code
    https://jestjs.io/docs/asynchronous

  3. axios - Cancellation
    https://github.com/axios/axios#cancellation

  4. Node.js v15に実装されたAbortController
    https://www.mitsue.co.jp/knowledge/blog/frontend/202012/14_0900.html

目次

  1. eslint を 6.8.0 → 7.32.0 へ、eslint-config-airbnb-base を 14.1.0 → 14.2.1 へ、eslint-config-prettier を 6.11.0 → 8.3.0 へ、eslint-plugin-import を 2.20.1 → 2.24.2 へ、eslint-plugin-prettier を 3.1.3 → 4.0.0 へバージョンアップする
  2. jest を 26.0.1 → 27.2.4 へバージョンアップする
  3. テストが失敗した原因を解消する
    1. The error below may be caused by using the wrong test environment, see https://jestjs.io/docs/configuration#testenvironment-string. Consider using the "jsdom" test environment.
    2. jQuery requires a window with a document
    3. Test functions cannot both take a 'done' callback and return something. Either use a 'done' callback, or return a promise.
    4. Jest has detected the following 2 open handles potentially keeping Jest from exiting:
    5. jest.setTimeout(...); を呼び出しても効かないようなので jest.config.json に testTimeout を設定する方法に切り替える

手順

eslint を 6.8.0 → 7.32.0 へ、eslint-config-airbnb-base を 14.1.0 → 14.2.1 へ、eslint-config-prettier を 6.11.0 → 8.3.0 へ、eslint-plugin-import を 2.20.1 → 2.24.2 へ、eslint-plugin-prettier を 3.1.3 → 4.0.0 へバージョンアップする

以下のコマンドを実行して eslint 関連のモジュールを最新のバージョンにします。

  • npm install --save-dev eslint@7.32.0
  • npm install --save-dev eslint-config-airbnb-base@14.2.1
  • npm install --save-dev eslint-config-prettier@8.3.0
  • npm install --save-dev eslint-plugin-import@2.24.2
  • npm install --save-dev eslint-plugin-prettier@4.0.0

npm test コマンドを実行するとテストは全て成功し、

f:id:ksby:20211001224700p:plain

npm run build コマンドも正常に終了しました。

f:id:ksby:20211001224838p:plain

途中で npx browserslist@latest --update-db のコマンドを実行するようメッセージが出力されているので、実行します。npm update は eslint 以外のモジュールもバージョンアップされるので(しかも必ずしも最新バージョンになる訳ではないらしい)実行しません。

f:id:ksby:20211001225203p:plain f:id:ksby:20211001225244p:plain

jest を 26.0.1 → 27.2.4 へバージョンアップする

以下のコマンドを実行します。

  • npm install --save-dev jest@27.2.4
  • npm install --save-dev jest-html-reporter@3.4.1

レポートファイルを削除したいので最初に gradle から clean タスクを実行しておきます。

npm test コマンドを実行するとテストが大量に失敗するようになりました。。。

f:id:ksby:20211001233149p:plain

build/reports/jest/jest-html-reporter.html にレポートが出力されていますが、failed の赤ばかりです。

f:id:ksby:20211002000004p:plain

エラーになったテストで出力されているメッセージは全部で4つあり、1つ目は The error below may be caused by using the wrong test environment, see https://jestjs.io/docs/configuration#testenvironment-string. Consider using the "jsdom" test environment.

f:id:ksby:20211001234806p:plain

2つ目は jQuery requires a window with a document

f:id:ksby:20211001235051p:plain

3つ目は Test functions cannot both take a 'done' callback and return something. Either use a 'done' callback, or return a promise.

f:id:ksby:20211001235235p:plain

4つ目は Jest has detected the following 2 open handles potentially keeping Jest from exiting:

f:id:ksby:20211001235442p:plain

テストが失敗した原因を解消する

The error below may be caused by using the wrong test environment, see https://jestjs.io/docs/configuration#testenvironment-string. Consider using the "jsdom" test environment.

エラーメッセージに出力されている https://jestjs.io/docs/configuration#testenvironment-string を見ると The test environment that will be used for testing. The default environment in Jest is a Node.js environment. If you are building a web app, you can use a browser-like environment through jsdom instead. という記述があり、テストファイルの先頭に @jest-environment docblock を追加して @jest-environment jsdom を記述すれば解決するようですが、さすがに各テストファイルに記述するのは避けたいところです。

jest.config.json"testEnvironment": "jsdom", を追加することにします。

{
  "coverageDirectory": "build/reports/jest",
  "moduleDirectories": [
    "node_modules",
    "src/main/assets/js"
  ],
  "testEnvironment": "jsdom",
  "reporters": [
    "default",
    [
      "./node_modules/jest-html-reporter",
      {
        "pageTitle": "Test Report"
      }
    ]
  ]
}

また Jest 27: New Defaults for Jest, 2021 editionFor this reason, we are changing the default test environment from "jsdom" to "node". という記述がありました。For this reason の前の文章を読むと jsdom はパフォーマンス・オーバーヘッドがあるので default を node に変更したとのこと。今後は必要なテストだけテストファイルの先頭に @jest-environment docblock を追加する方式にした方が良いのでしょう。

jQuery requires a window with a document

testEnvironment を jsdom に変更したら出力されなくなりました。

Test functions cannot both take a 'done' callback and return something. Either use a 'done' callback, or return a promise.

https://jestjs.io/blog/2020/05/05/jest-26#other-breaking-changes-in-jest-26[jest-circus] Fail tests if a test takes a done callback and have return value という記述がありました。

done callback を使っているとテストが失敗するようになったことは分かりましたが、これだけでは対応方法が分かりません。他に参考になるものがないか調べてみると Done.fail function is not working の中に Testing Asynchronous Code へのリンクがありました。このページに対応方法が記載されています。

リンク先の記述に従って修正すると以下のコードの場合には、

  test("Nock で 500 を返すと catch の方で処理される", async (done) => {
    nock("http://api.openweathermap.org")
      .get(/^\/data\/2\.5\/weather/)
      .reply(500);

    await openWeatherMapHelper
      .getCurrentWeatherDataByCityName("Tokyo")
      .then((response) => {
        done.fail("then に処理が来たらエラー");
      })
      .catch((e) => {
        expect(e.response.status).toBe(500);
      });

    // test("...", async done => { ...}); と done を記述している場合には、テストの最後で done(); を呼び出さないと
    // 5秒待機した後、
    // Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
    // のエラーメッセージを表示してテストが失敗する
    // ※https://facebook.github.io/jest/docs/en/asynchronous.html#callbacks 参照
    done();
  });

async/await を取り除いて Promise を返すようにするか(Promise を返せば Jest は Promise が完了するまで待つとのこと)、

  test("Nock で 500 を返すと catch の方で処理される", () => {
    expect.assertions(1);

    nock("http://api.openweathermap.org")
      .get(/^\/data\/2\.5\/weather/)
      .reply(500);

    return openWeatherMapHelper
      .getCurrentWeatherDataByCityName("Tokyo")
      .catch(e => expect(e.response.status).toBe(500));
  });

async/await を残すならば try...catch で以下のように記述すればテストが成功するようになります。

  test("Nock で 500 を返すと catch の方で処理される", async () => {
    expect.assertions(1);

    nock("http://api.openweathermap.org")
      .get(/^\/data\/2\.5\/weather/)
      .reply(500);

    try {
      await openWeatherMapHelper.getCurrentWeatherDataByCityName("Tokyo");
    } catch (e) {
      expect(e.response.status).toBe(500);
    }
  });

今回は後者の async/await を残して try...catch で対応することにします。

  • async (done)async () に変更する。
  • done(); あるいは done.~ を呼び出している箇所を削除する。
  • テストの最初に expect.assertions(1); を記述する(assertions メソッドに渡す数字はテストで expect が呼び出される回数を指定する)。https://jestjs.io/docs/expect#expectassertionsnumber を読むと async を付けたテストメソッドの場合、きちんとテストが実行されているのかを確認するために expect.assertions を書いた方が良いらしい。
  • await を付けて呼び出しているところを、await を取り除いて try...catch で囲む記述に変更する。

Jest has detected the following 2 open handles potentially keeping Jest from exiting:

package.json で jest を実行するコマンドに --detectOpenHandles オプションを付けているのでテスト終了時にオープンされたままのハンドラが検知・出力されるのですが、以前から付けていて Jest 27 にバージョンアップされたらこれまで検知されていなかったものが検知されるようになったようです。

  "scripts": {
    "test": "run-s prettier:format prettier:format-test jest",
    "jest": "jest --config=jest.config.json --coverage --runInBand --detectOpenHandles",
    ..........

axios のリクエストを cancel する方法が https://github.com/axios/axios#cancellation に記述されており、これを使えば解消しそうな気がするのですが、うまく実装できません。。。 テストは成功しているので、今回は特に何もしないことにします。

また https://jestjs.io/docs/cli#--detectopenhandles によると --detectOpenHandlesデバッグ時のみ使用するよう書かれていたので、テストが並列で実行されなくなる --runInBand オプションと合わせて外すことにします。

  "scripts": {
    "test": "run-s prettier:format prettier:format-test jest",
    "jest": "jest --config=jest.config.json --coverage",
    "jest:debug": "jest --config=jest.config.json --coverage --runInBand --detectOpenHandles",
    ..........

jest.setTimeout(...); を呼び出しても効かないようなので jest.config.json に testTimeout を設定する方法に切り替える

上記4つの調査でいろいろ試している時に気づいたのですが、テストメソッド内で jest.setTimeout(30000); を呼び出しても 5秒経過するとテストがエラーになりました。jest.config.json"testTimeout": 30000, を設定すれば有効になるようなので、こちらの設定に切り替えます。

{
  "coverageDirectory": "build/reports/jest",
  "moduleDirectories": [
    "node_modules",
    "src/main/assets/js"
  ],
  "testEnvironment": "jsdom",
  "testTimeout": 30000,
  "reporters": [
    "default",
    [
      "./node_modules/jest-html-reporter",
      {
        "pageTitle": "Test Report"
      }
    ]
  ]
}

npm test コマンドを実行すると A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks. というメッセージが出力されますが、テストは全て成功するようになりました。

f:id:ksby:20211003182726p:plain

build/reports/jest/jest-html-reporter.html のレポートも passed の緑のみになっています。

f:id:ksby:20211003183047p:plain

履歴

2021/10/03
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その94 )( Node.js を 12.16.3 → 14.18.0 へバージョンアップする )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その93 )( WebDriver を最新バージョンに上げる ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Node.js を 12.16.3 → 14.18.0 へバージョンアップします。npm は 6.14.5 のままです(npm 7 系は Node.js の 16.x から対応のようなので)。
    • これまで Node.js のバージョン管理ツールとして nodist を使用していましたが 30 Mar 2019 にリリースされた 0.9.1 でバージョンが止まっており、Node.js の 14.x 系へのバージョンアップもスムーズに出来ないようなのと、nvm-windows の 1.1.8 が2週間程前にちょうどリリースされたので、nvm-windows に切り替えることにします。
    • npm でインストールしているモジュールを一部を除き最新のバージョンにします。

参照したサイト・書籍

  1. 2020 年ではもう使えない Nodist はアンインストールする (Windows)
    https://zenn.dev/ymasaoka/articles/note-uninstall-nodish-windows

  2. coreybutler / nvm-windows
    https://github.com/coreybutler/nvm-windows

  3. node-gradle / gradle-node-plugin
    https://github.com/node-gradle/gradle-node-plugin

  4. Node.jsの新LTS版となる「Node.js 16」正式リリース。Apple M1チップ対応、JavaScriptエンジン「V8 9.0」搭載など新機能
    https://www.publickey1.jp/blog/21/nodejsltsnodejs_16apple_m1javascriptv8_90.html

    • Node.js は 16.x 系がリリースされていたはずと思っていたら、アクティブな LTS になるのはリリースから6ヶ月後の 10月下旬からでした。npm 7対応も 16.x からのようです。

目次

  1. まずは現在の状況を確認する
  2. nodist をアンインストールする
  3. nvm-windows をインストールして Node.js を 12.16.3 → 14.18.0 へバージョンアップする
  4. モジュールを最新のバージョンにする
  5. Gradle の npm-script 実行用のプラグインを com.moowork.node:1.3.1com.github.node-gradle.node:3.1.1 に変更する

手順

まずは現在の状況を確認する

現在のバージョンは nodist が 0.9.1、Node.js が 12.16.3、npm が 6.14.5 です。

f:id:ksby:20210930105814p:plain

https://nodejs.org/ja/ を見ると Node.js の推奨版は 14.18.0 LTS です。

f:id:ksby:20210930235949p:plain

nodist をアンインストールする

2020 年ではもう使えない Nodist はアンインストールする (Windows) の記事を見つけたので、ここに記載されている手順でアンインストールします。

コントロールパネルの「プログラムと機能」の画面を開き、nodist を選択してコンテキストメニューを表示してから「アンインストールと変更」を選択します。

f:id:ksby:20210930224041p:plain

D:\Nodist にインストールしていましたが、アンインストール後もこのディレクトリが残っているので削除します。

C:\Users\<ユーザ名>\.npmrc のファイルを削除します。

C:\Users\root\AppData\Roaming\npm-cacheディレクトリを削除します。

コマンドプロンプトで実行を試みると、nodist、Node.js、npm の全てが実行できなくなっていることが確認できました。

f:id:ksby:20210930225014p:plain

nvm-windows をインストールして Node.js を 12.16.3 → 14.18.0 へバージョンアップする

https://github.com/coreybutler/nvm-windows/releases の「1.1.8 - Corepack」から nvm-setup.zip をダウンロードします。

ダウンロードしたファイルを解凍すると nvm-setup.exe が生成されるので実行します。

  1. 「Setup - NVM for Windows」画面が表示されるので、「I accept the agreement」を選択後「Next」ボタンをクリックします。
  2. 「Select Destination Location」画面が表示されるので、インストール先を D:\nvm に変更後「Next」ボタンをクリックします。
  3. 「Set Node.js Symlink」画面が表示されるので、D:\nodejs に変更後「Next」ボタンをクリックします。
  4. 「Ready to install」画面が表示されるので、「Install」ボタンをクリックします。
  5. 「Completing the NVM for Windows Setup Wizard」画面が表示されるので、「Finish」ボタンをクリックします。

インストール後に環境変数を確認すると NVM_HOME、NVM_SYMLINK の2つが追加されて、この2つの環境変数を参照するよう PATH に追加されていました。

1.1.8 で nvm install lts というコマンドが追加されており、実行すると自動で現在の LTS である 14.18.0 がインストールされます。素晴らしい!

f:id:ksby:20210930234559p:plain

nvm use 14.18.0 コマンドを実行するよう書かれているので実行します。。。が、エラーになる上に SJIS文字コードだとエラーメッセージが文字化けして読めません。

f:id:ksby:20210930234739p:plain

nvm use 14.18.0 > nvm.log でファイルに出力してから IntelliJ IDEA で確認してみると、exit status 1: この操作を実行するための十分な特権がありません。 というエラーメッセージでした。

コマンドプロンプトを「管理者として実行」で起動してから nvm use 14.18.0 コマンドを実行すると、正常に実行されました。

f:id:ksby:20210930235136p:plain

管理者として実行していないコマンドプロンプトに戻ってバージョンを確認すると、nvm が 1.1.8、Node.js が 14.18.0、npm が 6.14.5 になっていました。

f:id:ksby:20210930235635p:plain

モジュールを最新のバージョンにする

バージョンアップ可能なモジュールを確認します。IntelliJ IDEA のメインメニューから「File」-「Settings...」を選択して「Settings」ダイアログを開き、画面左側で「Language & Frameworks」-「Node.js and NPM」を選択します。。。が、node、npm を認識していませんでした。後から気づきましたが、nvm use 14.18.0 コマンド実行後に IntelliJ IDEA を再起動し忘れているのが原因なので、手動で追加して認識させる必要はありませんでした。。。

f:id:ksby:20211001000613p:plain

手動で追加して認識させることにします。「Node interpreter」の右にある「...」ボタンをクリックします。

f:id:ksby:20211001001424p:plain

「Node interpreters」ダイアログが表示されるので、画面左上の「+」ボタンをクリックした後ドロップダウンメニューから「Add Local...」を選択します。

f:id:ksby:20211001001805p:plain

「Select Path」ダイアログが表示されるので、D:\nodejs\node.exe を選択して「OK」ボタンをクリックします。

f:id:ksby:20211001001843p:plain

D:\Nodist\... のパスが登録されていますが邪魔なので選択して「-」ボタンをクリックして削除します。

f:id:ksby:20211001002019p:plain

削除後「OK」ボタンをクリックしてダイアログを閉じて「Settings」ダイアログに戻ると、今度は画面右側にモジュールの一覧と現行バージョン、最新バージョン一覧が表示されました。

f:id:ksby:20211001002631p:plain f:id:ksby:20211001002736p:plain

今回は以下の方針でバージョンアップします。

  • admin-lte、bootstrap、ionicons は現行のまま。
  • mobx、mobx-utils は少し触ってみただけなのでバージョンアップしない。
  • eslint、jest、autoprefixer + cssnano + postcss-cli、prettier、stylelint + stylelint-config-standard、webpack + webpack-cli は別に章を分けてバージョンアップする。
  • バージョンアップは npm update ではなく npm install --save-dev autoprefixer@8.0.0(package.json の dependencies に記載されているものは --save、devDependencies に記載されているものは --save-dev にする) のようにバージョンを指定しながらバージョンアップする。
  • 1つずつ上からバージョンアップする。関連しそうなところを動作確認しながら進める。

特に問題がでなければ画面キャプチャは撮りません。

  • npm install --save-dev acorn@8.5.0
  • npm install --save-dev axios@0.21.4
  • npm install --save-dev browser-sync@2.27.5
  • npm install --save-dev http-proxy-middleware@2.0.1
  • npm install --save-dev jquery@3.6.0
  • npm install --save-dev jquery-mockjax@2.6.0
  • npm install --save-dev nock@13.1.3

npm install コマンドを実行してから(npm scripts の postinstall の処理を実行するため) npm test コマンドを実行すると全てのテストが成功しました。

f:id:ksby:20211001010223p:plain

npm run build コマンドも正常に終了しました。

f:id:ksby:20211001012513p:plain

clean タスク実行 → Rebuild Project 実行 → build タスク実行は npmTest タスクでエラーになりました。

f:id:ksby:20211001012923p:plain

原因は nvm use 14.18.0 コマンド実行後に IntelliJ IDEA を再起動していないため、D:\nodejsディレクトリ(コマンド実行後に作成されるシンボリックリンク)を認識できていないからでした。

IntelliJ IDEA を再起動すれば認識されるはずですが、Gradle の npm-script 実行用のプラグインは cmd.exe を呼び出さなくてもよい https://github.com/node-gradle/gradle-node-plugin が出ているので、切り替えることにします。

Gradle の npm-script 実行用のプラグインcom.moowork.node:1.3.1com.github.node-gradle.node:3.1.1 に変更する

build.gradle の以下の点を変更します。

plugins {
    ..........
    id "com.github.node-gradle.node" version "3.1.1"
    ..........
}

..........

task npmTest(type: NpmTask) {
    args = ["test"]
}
task npmBuild(type: NpmTask) {
    args = ["run", "build"]
}
npmBuild.dependsOn npmTest
processResources.dependsOn npmBuild
  • plugins block の以下の点を変更します。
    • id "com.moowork.node" version "1.3.1"id "com.github.node-gradle.node" version "3.1.1" に変更します。
  • npmTest タスクの以下の点を変更します。
    • type を ExecNpmTask に変更します。
    • commandLine "cmd", "/c", "npm test >NUL 2>&1"args = ["test"] に変更します。
  • npmBuild タスクの以下の点を変更します。
    • type を ExecNpmTask に変更します。
    • commandLine "cmd", "/c", "npm run build >NUL 2>&1"args = ["run", "build"] に変更します。

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

clean タスク実行 → Rebuild Project 実行 → build タスク実行は問題なく BUILD SUCCESSFUL のメッセージが出力されて成功し、

f:id:ksby:20211001020304p:plain

gebTest タスクも BUILD SUCCESSFUL のメッセージが出力されて成功しました。

f:id:ksby:20211001020845p:plain

今回は browser-sync と http-proxy-middleware をバージョンアップしたので npm run springboot で起動してみましたが特に問題なく起動しました。

f:id:ksby:20211001022513p:plain f:id:ksby:20211001022603p:plain

履歴

2021/10/01
初版発行。