10.2.3. 機能ごとのテスト実装

本節では、レイヤ単位に当てはめられない機能のテスト方法ついて説明する。


10.2.3.1. 入力チェックの単体テスト

ここでは、以下の入力チェックの単体テスト実装方法を説明する。

Validationの種類 説明
Bean Validation Hibernate validatorを使用して実装したValidatorのテスト
Bean Validation Spring のDIコンテナを使用して実装したValidatorのテスト
Spring Validation Spring Validationを使用して実装したValidatorのテスト
Validatorの単体テストは本来Controllerのテストとして行うが、その場合は試験パターンが多くなるため、テストの実施コストを考慮しControllerと切り分けてValidator単体としてテストを行うこともできる。
ここでは、Validator単体としてのテスト作成方法を説明する。

本節では、Bean Validationを使用している場合とSpring Validationを使用している場合のそれぞれについて実装方法を説明する。


10.2.3.1.1. Bean Validationで実装したValidatorの単体テスト

Bean Validationのテスト行う場合、アプリケーションサーバからライブラリが提供されないため、必要な依存ライブラリ追加する必要がある。追加方法については、依存ライブラリの追加を参照されたい。なお、Hibernate Validatorが用意する入力チェック機能についてはテストスコープ外とする。

ここでは、以下の2通りのBean Validationのテスト方法について説明する。

  • Hibernate validatorを使用したBean Validation
  • Spring のDIコンテナを使用したBean Validation

10.2.3.1.1.1. Hibernate validatorを使用したBean Validationのテスト

Hibernate validatorを使用したBean Validationの単体テストにおいて、作成するファイルを以下に示す。

../../_images/ImplementsOfTestByFunctionBeanValidationItems.png
作成するファイル名 説明
FullWidthKatakanaTest.java @FullWidthKatakanaアノテーションのテストクラス
FullWidthKatakanaTestBean.java @FullWidthKatakanaアノテーションをフィールドに付与したBeanクラス

ここでは、テスト対象の@FullWidthKatakanaを使用したBeanクラス(FullWidthKatakanaTestBean)を作成し、jakarta.validation.ValidatorFactoryから生成したjakarta.validation.Validatorの実装クラスによりバリデーションチェックを行っている。

以下に、@FullWidthKatakanaアノテーションをフィールドに付与したBeanクラスの作成例を示す。

  • FullWidthKatakanaTestBean.java
public class FullWidthKatakanaTestBean {

    @FullWidthKatakana
    private String testString;

    public FullWidthKatakanaTestBean() {
        // constructor
    }

    public String getTestString() {
        return testString;
    }

    public void setTestString(String testString) {
        this.testString = testString;
    }

}

  • FullWidthKatakanaTest.java
public class FullWidthKatakanaTest {

    private static Validator validator;

    @BeforeClass
    public static void setUpBeforeClass() {

        // setup
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();

        // (1)
        validator = validatorFactory.getValidator();
    }

    @Test
    public void testFullWidthKatakana() {

        // setup
        FullWidthKatakanaTestBean form = new FullWidthKatakanaTestBean();
        form.setTestString("デンデン");

        // run the test
        Set<ConstraintViolation<FullWidthKatakanaTestBean>> violations = validator.validate(form); // (2)

        // assert
        assertThat(violations, is(empty())); // (3)
    }
}
項番 説明
(1)
getValidatorメソッドにより、Validatorを取得する。
Validatorを取得することで、validateメソッドを使った入力チェックが可能となる。
(2)
validateメソッドを使い、入力チェックを行う。
validateメソッドを実行することで、入力チェックエラーの数だけConstrainViolationSetが返ってくる。 validateメソッドの引数にはFullWidthKatakanaBeanクラスのオブジェクトを指定する。
(3)
(2)で取得したSetから、エラーが発生したかどうかを確認する。
今回はエラーがないため、空のSetが返ってくる。

Note

バリデーショングループを使用したテスト

バリデーショングループを設定している場合、入力チェックを行なう際のvalidateメソッド引数に、グループを示す任意のjava.lang.Classオブジェクトを指定することで、指定したグループのValidatorのみ適用して実行できる。

バリデーショングループについては、バリデーションのグループ化を参照されたい。

以下に、バリデーショングループを使用したForm例を示す。

  • テスト対象の FullWidthKatakanaTestBean.java

    public class FullWidthKatakanaTestBean {
    
        public interface Search {};
        public interface Register {};
    
        // (1)
        @Size(min = 5, max = 10, groups = Search.class)
        @FullWidthKatakana(groups = Register.class)
        @NotNull
        private String testString;
    
        public FullWidthKatakanaTestBean() {
            // constructor
        }
    
        public String getTestString() {
            return testString;
        }
    
        public void setTestString(String testString) {
            this.testString = testString;
        }
    
    }
    
    項番 説明
    (1)
    フィールドに設定するValidatorをグループ化している。
  • FullWidthKatakanaTest.java

    public class FullWidthKatakanaTest {
    
        // omitted
    
        @Test
        public void testFullWidthKatakana() {
    
            // setup
            FullWidthKatakanaTestBean form = new FullWidthKatakanaTestBean();
            form.setTestString("テスト");
    
            // run the test
            // (1)
            Set<ConstraintViolation<FullWidthKatakanaTestBean>> violations =
                    validator.validate(form);
    
            // assert
            assertThat(violations, is(empty())); // (2)
        }
    }
    
    項番 説明
    (1)
    validateメソッドの引数に、java.lang.Classオブジェクトを追加することで、設定したバリデーショングループに対して入力チェックを実行できる。また、java.lang.Classオブジェクトは例のように複数指定することもできる。
    (2)
    エラーが発生したかどうかを確認する。

10.2.3.1.1.2. Spring のDIコンテナを使用したBean Validationのテスト

Spring のDIコンテナを使用したBean Validationの単体テストにおいて、作成するファイルを以下に示す。

../../_images/ImplementsOfTestByFunctionExistInCodeListItems.png
作成するファイル名 説明
ExistInCodeListTest.java Spring のDIコンテナを使用したBean Validationのテストクラス
test-context.xml Spring Testを使用して単体テストを行う際に必要な設定を補うための設定ファイル。

Spring のDIコンテナを利用したBean Validationは、org.springframework.validation.beanvalidation.LocalValidatorFactoryBeanからValidatorオブジェクトを生成することでテストすることができる。

ここでは、Spring のDIコンテナを利用した入力チェックとして@ExistInCodeListを例にテストの実装方法を説明する。
@ExistInCodeListについての詳細はコードリストを用いたコード値の入力チェックを参照されたい。

テストで使用する設定ファイルに、Validatorオブジェクトを生成するためのLocalValidatorFactoryBeanをBean定義する。

  • test-context.xml
<!-- (1) -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
項番 説明
(1)
@ExistInCodeListでDIコンテナからコードリストBeanを取得するため、test-context.xmlでBean定義したLocalValidatorFactoryBeanから生成したValidatorを使う必要がある。

以下に、@ExistInCodeListが使われているFormクラスの実装例を示す。

  • TicketSearchForm.java
public class TicketSearchForm implements Serializable {

    @NotNull
    @ExistInCodeList(codeListId = "CL_AIRPORT") // (1)
    private String depAirportCd;

    // omitted
}
項番 説明
(1)
depAirportCdフィールドに対して、コードリストに存在する値かどうか検証する。
以下に、@ExistInCodeListのテストクラス作成方法を説明する。
ここでは、sample-codelist.xmlに定義したコードリスト(CL_AIRPORT)に定義していない値を設定し、インジェクションしたjakarta.validation.Validatorの実装クラスによりバリデーションチェックエラーになることを確認している。
  • ExistInCodeListTest.java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
        "classpath:META-INF/spring/sample-infra.xml",
        "classpath:META-INF/spring/sample-codelist.xml",
        "classpath:META-INF/spring/test-context.xml" })
public class ExistInCodeListTest {

    // (1)
    @Inject
    private Validator validator;

    @Test
    public void testExistInCodeList() {

        // setup
        TicketSearchForm ticketSearchForm = new TicketSearchForm();
        // (2)
        ticketSearchForm.setDepAirportCd("AAA");

        // omitted

        // run the test
        Set<ConstraintViolation<TicketSearchForm>> violations = validator
                .validate(ticketSearchForm);

        // assert
        // (3)
        assertThat(violations.size(), is(1));
        ConstraintViolation<TicketSearchForm> violation = violations.iterator().next();
        // (4)
        assertThat(violation.getPropertyPath().toString(), is("depAirportCd"));
        // (5)
        assertThat((String) violation.getInvalidValue(), is("AAA"));
        // (6)
        assertThat(violation.getMessage(), is("Does not exist in CL_AIRPORT"));
    }
}
項番 説明
(1)
ValidatorにSpringのLocalValidatorFactoryBeanから生成したValidatorをDIしている。
LocalValidatorFactoryBeanから生成したValidatorはSpringのDIコンテナ上で動作し、@ContextConfigurationで読み込んだコードリストのBeanを取得することができる。
これにより、@ExistInCodeListを期待通りに動作させることができる。
(2)
コードリストに存在しないコードを入力し、@ExistInCodeListでエラーが発生することを期待する。
(3)
sizeメソッドを使って入力チェックエラーの数を取得し、エラーが発生したかどうかを確認する。
(4)
違反したフィールドが想定した箇所であるかを確認する。
(5)
違反した入力値が想定した値であるかを確認する。
(6)
発生したエラーのメッセージを確認する。

10.2.3.1.2. Spring Validatorで実装したValidatorの単体テスト

Validator(Spring Validation)の単体テストにおいて、作成するファイルを以下に示す。

../../_images/ImplementsOfTestByFunctionSpringValidationItems.png
作成するファイル名 説明
TicketSearchValidatorTest.java TicketSearchValidator.javaのテストクラス

以下に、テスト対象のクラスを示す。

  • TicketSearchValidator.java
@Component
public class TicketSearchValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return (TicketSearchForm.class).isAssignableFrom(clazz);
    }

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

        TicketSearchForm form = (TicketSearchForm) target;

        if (!errors.hasFieldErrors("depAirportCd")
            && !errors.hasFieldErrors("arrAirportCd")) {
            String depAirport = form.getDepAirportCd();
            String arrAirport = form.getArrAirportCd();
            if (depAirport.equals(arrAirport)) {
                errors.reject(TicketSearchErrorCode.E_AR_B1_5001.code());
            }
        }

        // omitted
    }
}
以下に、Validator(Spring Validation)のテストクラス作成方法を説明する。
ここでは、テスト対象のTicketSearchValidatorでエラーになる値をTicketSearchFormに設定してバリデーションエラーになることと、エラーメッセージが正しいことを確認している。
  • TicketSearchValidatorTest.java
public class TicketSearchValidatorTest {

    private static TicketSearchValidator validator;

    private TicketSearchForm ticketSearchForm;

    private BindingResult result;

    @BeforeClass
    public static void setUpBeforeClass() {

        // setup
        validator = new TicketSearchValidator();
    }

    @Test
    public void testTicketSearchValidator() {

        // setup
        ticketSearchForm = new TicketSearchForm();
        result = new DirectFieldBindingResult(ticketSearchForm, "TicketSearchForm");

        ticketSearchForm.setFlightType(FlightType.RT);
        ticketSearchForm.setDepAirportCd("HND");
        ticketSearchForm.setArrAirportCd("HND");
        // omitted

        // run the test
        // (1)
        validator.validate(ticketSearchForm, result);

        // (2)
        assertThat(result.hasErrors(), is(true));

        // (3)
        ObjectError error = result.getGlobalError();

        // (4)
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        // (5)
        messageSource.setBasename("i18n/sample-messages");
        // (6)
        messageSource.setUseCodeAsDefaultMessage(true);

        String code = error.getCode();
        // assert
        // (7)
        assertThat(code, is(TicketSearchErrorCode.E_AR_B1_5001.code()));
        assertThat(messageSource.getMessage(error, Locale.JAPAN),
                is("出発空港と到着空港に同じ空港は指定できません。区間をご確認ください。"));
    }
}
項番 説明
(1)
validateメソッドの引数に、ticketSearchFormと、BindingResultインターフェースのオブジェクトを指定することで、ticketSearchFormに対する入力チェックの結果が、BindingResultクラスのオブジェクトに格納される。
(2)
hasErrorsメソッドを使って、エラーの有無を判定する。
エラーがある場合はtrueが返り値として返り、エラーがない場合はfalseが返り値として返る。
(3)
getGlobalErrorメソッドで、エラー内容を取得する。
(4)
エラーメッセージの内容を確認するために、MessageSourceの実装クラスであるorg.springframework.context.support.ResourceBundleMessageSourceのオブジェクトを生成する。
クラスの詳細については、ResourceBundleMessageSourceのJavadocを参照されたい。
(5)
setBasenameメソッドに、メッセージが定義されたプロパティファイルを指定して読み込ませる。
(6)
setUseCodeAsDefaultMessageメソッドにtrueを指定すると、エラーコードに対応するメッセージが定義されていない場合にエラーコードが返される。
falseを指定すると、エラーコードに対応するメッセージが定義されていない場合にNoSuchMessageExceptionが返される。
デフォルトではfalseが適用されている。
(7)
エラーコード、メッセージ内容を検証する。