かんがるーさんの日記

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

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その21 )( 入力画面1を作成する5 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その20 )( 入力画面1を作成する4 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 入力画面1の作成
    • サーバ側のテストを作成します。

参照したサイト・書籍

  1. Learning Ant path style
    https://stackoverflow.com/questions/2952196/learning-ant-path-style

目次

  1. InquiryInput01Form クラスのテストを作成する
  2. ksbysample-webapp-lending から TestHelper, HtmlResultMatchers クラスをコピーする
  3. HtmlResultMatchers クラスに val メソッドを追加する
  4. テストデータを用意する
  5. InquiryInputController クラスのテストを作成する
  6. 次回は。。。

手順

InquiryInput01Form クラスのテストを作成する

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput01Form.java で Ctrl+Shift+T を押して「Create Test」ダイアログを表示してから、以下の画像の値にした後「OK」ボタンをクリックします。

f:id:ksby:20170909065713p:plain

src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput01FormTest.groovy が新規作成されるので、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.web.inquiry.form

import spock.lang.Specification
import spock.lang.Unroll

import javax.validation.ConstraintViolation
import javax.validation.Validation

class InquiryInput01FormTest extends Specification {

    def validator
    def inquiryInput01Form

    def setup() {
        validator = Validation.buildDefaultValidatorFactory().getValidator()
        inquiryInput01Form = new InquiryInput01Form(
                lastname: "田中"
                , firstname: "太郎"
                , lastkana: "たなか"
                , firstkana: "たろう"
                , sex: "1"
                , age: "30"
                , job: "1")
    }

    def "placeholder で表示している例の Bean Validation のテスト"() {
        when:
        Set<ConstraintViolation<InquiryInput01Form>> constraintViolations =
                validator.validate(inquiryInput01Form)

        then:
        constraintViolations.size() == 0
    }

    @Unroll
    def "lastname の Bean Validation の Bean Validation のテスト(#lastname --> #size)"() {
        setup:
        inquiryInput01Form.lastname = lastname
        Set<ConstraintViolation<InquiryInput01Form>> constraintViolations =
                validator.validate(inquiryInput01Form)

        expect:
        constraintViolations.size() == size

        where:
        lastname || size
        ""       || 2
        "a"      || 0
        "a" * 20 || 0
        "a" * 21 || 1
    }

    @Unroll
    def "firstname の Bean Validation のテスト(#firstname --> #size)"() {
        setup:
        inquiryInput01Form.firstname = firstname
        Set<ConstraintViolation<InquiryInput01Form>> constraintViolations =
                validator.validate(inquiryInput01Form)

        expect:
        constraintViolations.size() == size

        where:
        firstname || size
        ""        || 2
        "a"       || 0
        "a" * 20  || 0
        "a" * 21  || 1
    }

    @Unroll
    def "lastkana の Bean Validation のテスト(#lastkana --> #size)"() {
        setup:
        inquiryInput01Form.lastkana = lastkana
        Set<ConstraintViolation<InquiryInput01Form>> constraintViolations =
                validator.validate(inquiryInput01Form)

        expect:
        constraintViolations.size() == size

        where:
        lastkana || size
        ""       || 3
        "a"      || 1
        "a" * 20 || 1
        "a" * 21 || 2
        "あ"      || 0
        "あ" * 20 || 0
        "あ" * 21 || 1
        "ア"      || 1
        "A"      || 1
        "1"      || 1
        "あa"     || 1
    }

    @Unroll
    def "firstkana の Bean Validation のテスト(#firstkana --> #size)"() {
        setup:
        inquiryInput01Form.firstkana = firstkana
        Set<ConstraintViolation<InquiryInput01Form>> constraintViolations =
                validator.validate(inquiryInput01Form)

        expect:
        constraintViolations.size() == size

        where:
        firstkana || size
        ""        || 3
        "a"       || 1
        "a" * 20  || 1
        "a" * 21  || 2
        "あ"       || 0
        "あ" * 20  || 0
        "あ" * 21  || 1
        "ア"       || 1
        "A"       || 1
        "1"       || 1
        "あa"      || 1
    }

    @Unroll
    def "sex の Bean Validation のテスト(#sex --> #size)"() {
        setup:
        inquiryInput01Form.sex = sex
        Set<ConstraintViolation<InquiryInput01Form>> constraintViolations =
                validator.validate(inquiryInput01Form)

        expect:
        constraintViolations.size() == size

        where:
        sex || size
        ""  || 1
        "0" || 1
        "1" || 0
        "2" || 0
        "3" || 1
    }

    @Unroll
    def "age の Bean Validation のテスト(#age --> #size)"() {
        setup:
        inquiryInput01Form.age = age
        Set<ConstraintViolation<InquiryInput01Form>> constraintViolations =
                validator.validate(inquiryInput01Form)

        expect:
        constraintViolations.size() == size

        where:
        age    || size
        ""     || 2
        "0"    || 0
        "1"    || 0
        "999"  || 0
        "1000" || 1
        "0.1"  || 1
    }

    @Unroll
    def "job の Bean Validation のテスト(#job --> #size)"() {
        setup:
        inquiryInput01Form.job = job
        Set<ConstraintViolation<InquiryInput01Form>> constraintViolations =
                validator.validate(inquiryInput01Form)

        expect:
        constraintViolations.size() == size

        where:
        job || size
        ""  || 0
        "0" || 1
        "1" || 0
        "2" || 0
        "3" || 0
        "4" || 1
    }

}

テストを実行して全て成功することを確認します。

f:id:ksby:20170909070152p:plain

ksbysample-webapp-lending から TestHelper, HtmlResultMatchers クラスをコピーする

テストに使用したいので、ksbysample-webapp-lending から TestHelper, HtmlResultMatchers クラスをコピーします。

src/test/java の下に ksbysample.common.test.helper パッケージを作成します。

src/test/java/ksbysample/common/test/helper の下に TestHelper.java をコピーします。

src/test/java/ksbysample/common/test の下に matcher パッケージを作成します。

src/test/java/ksbysample/common/test/matcher の下に HtmlResultMatchers.java をコピーします。

HtmlResultMatchers クラスは jsoup が必要なので導入します。build.gradle の以下の点を変更します。

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

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    ..........
    testCompile("com.google.code.findbugs:jsr305:3.0.2")
    testCompile("org.jsoup:jsoup:1.10.3")

    ..........
}
  • testCompile("org.jsoup:jsoup:1.10.3") を追加します。

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

HtmlResultMatchers クラスに val メソッドを追加する

今回のテストでは value 属性の値をチェックしたいのですが、今の HtmlResultMatchers クラスには value 属性を検証するためのメソッドが用意されていないので追加します。

src/test/java/ksbysample/common/test/matcher/HtmlResultMatchers.java の以下の点を変更します。

public class HtmlResultMatchers {

    ..........

    public ResultMatcher text(final String expectedText) {
        return mvcResult -> assertThat(selectFirst(mvcResult).text(), is(expectedText));
    }

    /**
     * HTML 内の cssQuery で指定された Element の value 属性の値を取得し、
     * 引数で渡された文字列と同じかチェックする
     *
     * @param expectedText 文字列の期待値
     * @return {@link ResultMatcher}
     */
    public ResultMatcher val(final String expectedText) {
        return mvcResult -> assertThat(selectFirst(mvcResult).val(), is(expectedText));
    }

    ..........

}
  • val メソッドを追加します。

テストデータを用意する

src/test/resources の下に ksbysample/webapp/bootnpmgeb/web/inquiry ディレクトリを新規作成します。

src/test/resources/ksbysample/webapp/bootnpmgeb/web/inquiry の下に InquiryInput01Form_001.yaml を新規作成し、以下の内容を記述します。

!!ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput01Form
# 最大文字数のデータ、選択肢の場合には一番最後のデータをセットする
lastname: 12345678901234567890
firstname: 12345678901234567890
lastkana: あいうえおかきくけこさしすせそたちつてと
firstkana: なにぬねのはひふへほまみむめもあいうえお
sex: 2
age: 999
job: 3

InquiryInputController クラスのテストを作成する

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputController.java で Ctrl+Shift+T を押して「Create Test」ダイアログを表示してから、以下の画像の値にした後「OK」ボタンをクリックします。

f:id:ksby:20170913001740p:plain

MockMvc を使用するので Groovy + Spock ではなく Groovy + JUnit4 で作成するのですが、「Create Test」ダイアログで JUnit4 を選択すると拡張子が .java になってしまうので、Spock を選択してファイルを生成してから中身を Groovy + JUnit4 に変更します。

src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputControllerTest.groovy が新規作成されるので、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.web.inquiry

import ksbysample.common.test.helper.TestHelper
import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput01Form
import org.junit.Before
import org.junit.Test
import org.junit.experimental.runners.Enclosed
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.mock.web.MockHttpSession
import org.springframework.test.context.junit4.SpringRunner
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.MvcResult
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import org.springframework.web.context.WebApplicationContext
import org.yaml.snakeyaml.Yaml

import static ksbysample.common.test.matcher.HtmlResultMatchers.html
import static org.assertj.core.api.Assertions.catchThrowable
import static org.assertj.core.api.AssertionsForClassTypes.assertThat
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@RunWith(Enclosed)
class InquiryInputControllerTest {

    @RunWith(SpringRunner)
    @SpringBootTest
    static class 入力画面1のテスト {
        private InquiryInput01Form inquiryInput01Form_001 =
                (InquiryInput01Form) new Yaml().load(getClass().getResourceAsStream("InquiryInput01Form_001.yaml"))

        @Autowired
        private WebApplicationContext context

        MockMvc mockMvc

        @Before
        void setup() {
            mockMvc = MockMvcBuilders.webAppContextSetup(context)
                    .build()
        }

        @Test
        void "初期表示時は画面の項目には何もセットされない"() {
            expect:
            mockMvc.perform(get("/inquiry/input/01"))
                    .andExpect(status().isOk())
                    .andExpect(html("#lastname").val(""))
                    .andExpect(html("#firstname").val(""))
                    .andExpect(html("#lastkana").val(""))
                    .andExpect(html("#firstkana").val(""))
                    .andExpect(html("input[name='sex'][checked='checked']").notExists())
                    .andExpect(html("#age").val(""))
                    .andExpect(html("select[name=job] option[selected]").notExists())
        }

        @Test
        void "項目全てに入力して入力画面2へ遷移してから戻ると以前入力したデータがセットされて表示される"() {
            expect: "項目全てに入力して「次へ」ボタンをクリックする"
            MvcResult result = mockMvc.perform(
                    TestHelper.postForm("/inquiry/input/01?move=next", inquiryInput01Form_001))
                    .andExpect(status().isFound())
                    .andExpect(redirectedUrlPattern("**/inquiry/input/02"))
                    .andReturn()
            MockHttpSession session = result.getRequest().getSession()

            and: "再び入力画面1を表示する"
            mockMvc.perform(get("/inquiry/input/01").session(session))
                    .andExpect(status().isOk())
                    .andExpect(html("#lastname").val(inquiryInput01Form_001.lastname))
                    .andExpect(html("#firstname").val(inquiryInput01Form_001.firstname))
                    .andExpect(html("#lastkana").val(inquiryInput01Form_001.lastkana))
                    .andExpect(html("#firstkana").val(inquiryInput01Form_001.firstkana))
                    .andExpect(html("input[name='sex'][checked='checked']").val(inquiryInput01Form_001.sex))
                    .andExpect(html("#age").val(inquiryInput01Form_001.age))
                    .andExpect(html("select[name=job] option[selected]").val(inquiryInput01Form_001.job))
        }

        @Test
        void "入力チェックエラーのあるデータで入力画面2へ遷移しようとするとIllegalArgumentExceptionが発生する"() {
            setup: "入力チェックエラーになるデータを用意する"
            inquiryInput01Form_001.lastname = "x" * 21

            expect: "入力画面1の「次へ」ボタンをクリックする"
            Throwable thrown = catchThrowable({
                mockMvc.perform(
                        TestHelper.postForm("/inquiry/input/01?move=next", inquiryInput01Form_001))
                        .andExpect(status().isOk())
            })
            assertThat(thrown.cause).isInstanceOf(IllegalArgumentException)
        }

    }

}

テストを実行して全て成功することを確認します。

f:id:ksby:20170913002519p:plain

次回は。。。

入力画面2を作成します。

履歴

2017/09/13
初版発行。