かんがるーさんの日記

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

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

概要

記事一覧はこちらです。

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

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

参照したサイト・書籍

  1. Verify Static Method Call using PowerMockito 1.6
    https://stackoverflow.com/questions/34323909/verify-static-method-call-using-powermockito-1-6

目次

  1. InquiryInput02Form クラスのテストを作成する
  2. InquiryInput02FormNotEmptyRule クラスのテストを作成する
  3. EmailValidator クラスのテストを作成する
  4. InquiryInput02FormValidator クラスのテストを作成する

手順

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

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

f:id:ksby:20171014144544p:plain

src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput02FormTest.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 InquiryInput02FormTest extends Specification {

    def validator
    def inquiryInput02Form

    def setup() {
        validator = Validation.buildDefaultValidatorFactory().getValidator()
        inquiryInput02Form = new InquiryInput02Form(
                zipcode1: "102"
                , zipcode2: "0072"
                , address: "東京都千代田区飯田橋1-1"
                , tel1: "03"
                , tel2: "1234"
                , tel3: "5678"
                , email: "taro.tanaka@sample.co.jp")
    }

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

        then:
        constraintViolations.size() == 0
    }

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

        expect:
        constraintViolations.size() == size

        where:
        zipcode1 || size
        ""       || 0
        "1"      || 0
        "1" * 3  || 0
        "1" * 4  || 1
    }

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

        expect:
        constraintViolations.size() == size

        where:
        zipcode2 || size
        ""       || 0
        "1"      || 0
        "1" * 4  || 0
        "1" * 5  || 1
    }

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

        expect:
        constraintViolations.size() == size

        where:
        address   || size
        ""        || 0
        "a"       || 0
        "a" * 256 || 0
        "a" * 257 || 1
    }

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

        expect:
        constraintViolations.size() == size

        where:
        tel1    || size
        ""      || 0
        "1"     || 0
        "1" * 5 || 0
        "1" * 6 || 1
    }

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

        expect:
        constraintViolations.size() == size

        where:
        tel2    || size
        ""      || 0
        "1"     || 0
        "1" * 4 || 0
        "1" * 5 || 1
    }

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

        expect:
        constraintViolations.size() == size

        where:
        tel3    || size
        ""      || 0
        "1"     || 0
        "1" * 4 || 0
        "1" * 5 || 1
    }

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

        expect:
        constraintViolations.size() == size

        where:
        email     || size
        ""        || 0
        "a"       || 0
        "a" * 256 || 0
        "a" * 257 || 1
    }

}

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

f:id:ksby:20171014151810p:plain

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

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

f:id:ksby:20171014150940p:plain

src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput02FormNotEmptyRuleTest.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 InquiryInput02FormNotEmptyRuleTest extends Specification {

    def validator
    def inquiryInput02FormNotEmptyRule

    def setup() {
        validator = Validation.buildDefaultValidatorFactory().getValidator()
        inquiryInput02FormNotEmptyRule = new InquiryInput02FormNotEmptyRule(
                zipcode1: "102"
                , zipcode2: "0072"
                , address: "東京都千代田区飯田橋1-1"
                , tel1: "03"
                , tel2: "1234"
                , tel3: "5678"
                , email: "taro.tanaka@sample.co.jp")
    }

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

        then:
        constraintViolations.size() == 0
    }

    @Unroll
    def "zipcode1 の NotEmpty のテスト(#zipcode1 --> #size)"() {
        setup:
        inquiryInput02FormNotEmptyRule.zipcode1 = zipcode1
        Set<ConstraintViolation<InquiryInput02FormNotEmptyRule>> constraintViolations =
                validator.validate(inquiryInput02FormNotEmptyRule)

        expect:
        constraintViolations.size() == size

        where:
        zipcode1 || size
        ""       || 1
        "1"      || 0
    }

    @Unroll
    def "zipcode2 の NotEmpty のテスト(#zipcode2 --> #size)"() {
        setup:
        inquiryInput02FormNotEmptyRule.zipcode2 = zipcode2
        Set<ConstraintViolation<InquiryInput02FormNotEmptyRule>> constraintViolations =
                validator.validate(inquiryInput02FormNotEmptyRule)

        expect:
        constraintViolations.size() == size

        where:
        zipcode2 || size
        ""       || 1
        "1"      || 0
    }

    @Unroll
    def "address の NotEmpty のテスト(#address --> #size)"() {
        setup:
        inquiryInput02FormNotEmptyRule.address = address
        Set<ConstraintViolation<InquiryInput02FormNotEmptyRule>> constraintViolations =
                validator.validate(inquiryInput02FormNotEmptyRule)

        expect:
        constraintViolations.size() == size

        where:
        address || size
        ""      || 1
        "a"     || 0
    }

}

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

f:id:ksby:20171014152525p:plain

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

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

f:id:ksby:20171014185311p:plain

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

package ksbysample.webapp.bootnpmgeb.util.validator

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

class EmailValidatorTest extends Specification {

    @Unroll
    def "メールアドレスの Validation のテスト(#email --> #result)"() {
        expect:
        EmailValidator.validate(email) == result

        where:
        email                                                                   || result
        ""                                                                      || true
        "@"                                                                     || false
        "a@"                                                                    || false
        "@b"                                                                    || false
        "a@b"                                                                   || true
        "@@"                                                                    || false
        "a@@b"                                                                  || false
        "a@b@c"                                                                 || false
        // ASCII文字だけなので OK
        "taro.tanaka@sample.co.jp"                                              || true
        "1234567890@1234567890"                                                 || true
        "ABCDEFGHOJKLMNOPQRSTUVWXYZ@ABCDEFGHOJKLMNOPQRSTUVWXYZ"                 || true
        "abcdefghojklmnopqrstuvwxyz@abcdefghojklmnopqrstuvwxyz"                 || true
        "!\"#\$%&'()*+,-./:;<=>?[\\]^_`{|}~@!\"#\$%&'()*+,-./:;<=>?[\\]^_`{|}~" || true
        // スペースがあるので NG
        "taro tanaka@sample.co.jp"                                              || false
        "taro.tanaka@sample co.jp"                                              || false
        // 非ASCII文字があるので NG
        "田中太郎@sample.co.jp"                                                     || false
        "taro.tanaka@サンプル co.jp"                                                || false
    }

}

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

f:id:ksby:20171014192308p:plain

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

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

f:id:ksby:20171014153257p:plain

src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput02FormValidatorTest.groovy が新規作成されるので、以下の内容を記述します。メールアドレスの入力チェックについては

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

import ksbysample.common.test.helper.TestHelper
import ksbysample.webapp.bootnpmgeb.util.validator.EmailValidator
import org.junit.Before
import org.junit.Test
import org.junit.experimental.runners.Enclosed
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PowerMockIgnore
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import org.powermock.modules.junit4.PowerMockRunnerDelegate
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit4.SpringRunner
import org.springframework.validation.Errors
import spock.lang.Specification
import spock.lang.Unroll

@RunWith(Enclosed)
class InquiryInput02FormValidatorTest {

    @SpringBootTest
    static class InquiryInput02FormValidator_メールアドレス以外 extends Specification {

        @Autowired
        private InquiryInput02FormValidator input02FormValidator

        Errors errors
        InquiryInput02Form inquiryInput02Form

        def setup() {
            errors = TestHelper.createErrors()
            inquiryInput02Form = new InquiryInput02Form(
                    zipcode1: "102"
                    , zipcode2: "0072"
                    , address: "東京都千代田区飯田橋1-1"
                    , tel1: "03"
                    , tel2: "1234"
                    , tel3: "5678"
                    , email: "taro.tanaka@sample.co.jp")
        }

        def "placeholder で表示している例の Bean Validation のテスト"() {
            setup:
            input02FormValidator.validate(inquiryInput02Form, errors)

            expect:
            errors.hasErrors() == false
        }

        @Unroll
        def "郵便番号の Validation のテスト(#zipcode1,#zipcode2 --> #hasErrors,#size)"() {
            setup:
            inquiryInput02Form.zipcode1 = zipcode1
            inquiryInput02Form.zipcode2 = zipcode2

            expect:
            input02FormValidator.validate(inquiryInput02Form, errors)
            errors.hasErrors() == hasErrors
            errors.getAllErrors().size() == size

            where:
            zipcode1 | zipcode2 || hasErrors | size
            ""       | ""       || false     | 0
            "999"    | ""       || true      | 1
            ""       | "9999"   || true      | 1
            "999"    | "9999"   || false     | 0
        }

        @Unroll
        def "電話番号の Validation のテスト(#tel1,#tel2,#tel3 --> #hasErrors,#size)"() {
            given:
            inquiryInput02Form.tel1 = tel1
            inquiryInput02Form.tel2 = tel2
            inquiryInput02Form.tel3 = tel3
            inquiryInput02Form.email = email

            when:
            input02FormValidator.validate(inquiryInput02Form, errors)

            then:
            errors.hasErrors() == hasErrors
            errors.getAllErrors().size() == size
            // メールアドレスのチェックは行われていないことをチェックする
            0 * EmailValidator.validate(email)

            where:
            tel1    | tel2   | tel3   | email || hasErrors | size
            ""      | ""     | ""     | ""    || true      | 1
            "03"    | ""     | ""     | ""    || true      | 1
            ""      | "1234" | ""     | ""    || true      | 1
            ""      | ""     | "5678" | ""    || true      | 1
            "03"    | "1234" | "5678" | ""    || false     | 0
            "3"     | "1234" | "5678" | ""    || true      | 1
            "03"    | "123"  | "5678" | ""    || true      | 1
            "03123" | "4"    | "5678" | ""    || false     | 0
            "03"    | "1234" | "567"  | ""    || true      | 1
        }

    }

    @RunWith(PowerMockRunner)
    @PowerMockRunnerDelegate(SpringRunner)
    @SpringBootTest
    @PrepareForTest(EmailValidator)
    @PowerMockIgnore("javax.management.*")
    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(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
        }

    }

}

テストを実行して全て成功することを確認します。。。が、1つ失敗しました。電話番号、メールアドレスが全て空の場合に入力チェックが OK になってしまうようです。

f:id:ksby:20171015070055p:plain

src/main/assets/js/inquiry/input02.js と src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput02FormValidator.java で最初の方の処理が一致していないことが原因でした。InquiryInput02FormValidator クラスの方も ignoreCheckRequired のようなものを入れる必要がありますね。。。

    var validateFunction = function () {
        if (validator.ignoreCheckRequired && form.isAllEmpty(idList)) {
            return;
        }

        if (form.isAllEmpty(idList)) {
            var errmsg = "電話番号とメールアドレスのいずれか一方を入力してください";
            form.setError(telIdFormGroup, errmsg);
            form.setError(emailIdFormGroup, errmsg);
            throw new Error(errmsg);
        } else {
        if (StringUtils.isEmpty(tel1)
                && StringUtils.isEmpty(tel2)
                && StringUtils.isEmpty(tel3)
                && StringUtils.isEmpty(email)) {
            return;
        }

        if (StringUtils.isEmpty(tel1 + tel2 + tel3)
                && StringUtils.isEmpty(email)) {
            errors.reject("InquiryInput02Form.telOrEmail.NotEmpty");
        } else {

以下の内容で修正することにします。

  • input02.html に <input type="hidden" name="ignoreCheckRequired" ... /> を追加します。
  • input02 で必須チェック不要な時には <input type="hidden" name="ignoreCheckRequired" ... /> にも true をセットします。
  • InquiryInput02Form クラスに ignoreCheckRequired を用意します。
  • InquiryInput02FormValidator で InquiryInput02Form.ignoreCheckRequired == true なら一番最初の必須チェックは行わないようにします。

src/main/resources/templates/web/inquiry/input02.html の以下の点を変更します。

          <!--/*@thymesVar id="inquiryInput02Form" type="ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput02Form"*/-->
          <form id="inquiryInput02Form" class="form-horizontal" method="post" action=""
                th:action="@{/inquiry/input/02/}"
                th:object="${inquiryInput02Form}">
            <input type="hidden" name="copiedFromSession" id="copiedFromSession" th:value="*{copiedFromSession}"/>
            <input type="hidden" name="ignoreCheckRequired" id="ignoreCheckRequired" value="false"/>
  • <input type="hidden" name="ignoreCheckRequired" id="ignoreCheckRequired" value="false"/> を追加します。value は固定で false にします。

src/main/assets/js/inquiry/input02.js の以下の点を変更します。

var btnBackOrNextClickHandler = function (event, url, ignoreCheckRequired) {
    ..........

    // サーバにリクエストを送信する
    $("#ignoreCheckRequired").val(ignoreCheckRequired);
    $("#inquiryInput02Form").attr("action", url);
    $("#inquiryInput02Form").submit();

    // return false は
    // event.preventDefault() + event.stopPropagation() らしい
    return false;
};
  • btnBackOrNextClickHandler 関数の以下の点を変更します。
    • $("#ignoreCheckRequired").val(ignoreCheckRequired); を追加します。

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput02Form.java の以下の点を変更します。

@Data
public class InquiryInput02Form implements Serializable {

    ..........

    private boolean ignoreCheckRequired = false;

}
  • private boolean ignoreCheckRequired = false; を追加します。

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput02FormValidator.java の以下の点を変更します。

@Component
public class InquiryInput02FormValidator implements Validator {

    ..........

    @Override
    public void validate(Object target, Errors errors) {
        ..........

        checkTelAndEmail(inquiryInput02Form.isIgnoreCheckRequired()
                , inquiryInput02Form.getTel1()
                , inquiryInput02Form.getTel2()
                , inquiryInput02Form.getTel3()
                , inquiryInput02Form.getEmail()
                , errors);
    }

    ..........

    @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ConfusingTernary"})
    private void checkTelAndEmail(boolean ignoreCheckRequired, String tel1, String tel2, String tel3, String email, Errors errors) {
        if (ignoreCheckRequired
                && StringUtils.isEmpty(tel1)
                && StringUtils.isEmpty(tel2)
                && StringUtils.isEmpty(tel3)
                && StringUtils.isEmpty(email)) {
            return;
        }

        ..........
    }

}
  • checkTelAndEmail メソッドの以下の点を変更します。
    • 引数に boolean ignoreCheckRequired を追加します。
    • 最初の if 文に ignoreCheckRequired の条件を追加します。
  • validate メソッド内で checkTelAndEmail メソッドを呼び出している部分の引数に inquiryInput02Form.isIgnoreCheckRequired() を追加します。

画面を動かして動作に問題がないことを確認した後(画面キャプチャは省略します)、src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput02FormValidatorTest.groovy の以下の点を変更します。

    @SpringBootTest
    static class InquiryInput02FormValidator_メールアドレス以外 extends Specification {

        ..........

        @Unroll
        def "電話番号の Validation のテスト(#tel1,#tel2,#tel3,#ignoreCheckRequired --> #hasErrors,#size)"() {
            given:
            inquiryInput02Form.tel1 = tel1
            inquiryInput02Form.tel2 = tel2
            inquiryInput02Form.tel3 = tel3
            inquiryInput02Form.email = email
            inquiryInput02Form.ignoreCheckRequired = ignoreCheckRequired

            when:
            input02FormValidator.validate(inquiryInput02Form, errors)

            then:
            errors.hasErrors() == hasErrors
            errors.getAllErrors().size() == size
            // メールアドレスのチェックは行われていないことをチェックする
            0 * EmailValidator.validate(email)

            where:
            tel1    | tel2   | tel3   | email | ignoreCheckRequired || hasErrors | size
            ""      | ""     | ""     | ""    | false               || true      | 1
            ""      | ""     | ""     | ""    | true                || false     | 0
            "03"    | ""     | ""     | ""    | false               || true      | 1
            ""      | "1234" | ""     | ""    | false               || true      | 1
            ""      | ""     | "5678" | ""    | false               || true      | 1
            "03"    | "1234" | "5678" | ""    | false               || false     | 0
            "3"     | "1234" | "5678" | ""    | false               || true      | 1
            "03"    | "123"  | "5678" | ""    | false               || true      | 1
            "03123" | "4"    | "5678" | ""    | false               || false     | 0
            "03"    | "1234" | "567"  | ""    | false               || true      | 1
        }

    }
  • InquiryInput02FormValidator_メールアドレス以外 クラスの以下の点を変更します。
    • 電話番号の Validation のテスト メソッドのメソッド名に ,#ignoreCheckRequired を追加します。
    • given: のところで、inquiryInput02Form.ignoreCheckRequired = ignoreCheckRequired を追加します。
    • where:ignoreCheckRequired を追加しテスト用の値も記述します。

テストを実行すると今度は全て成功しました。

f:id:ksby:20171015164144p:plain

履歴

2017/10/15
初版発行。