10.2.3. 機能ごとのテスト実装¶
本節では、レイヤ単位に当てはめられない機能のテスト方法ついて説明する。
10.2.3.1. 入力チェックの単体テスト¶
ここでは、以下の入力チェックの単体テスト実装方法を説明する。
Validationの種類  | 
説明  | 
|---|---|
Bean Validation  | 
Hibernate validatorを使用して実装した  | 
Bean Validation  | 
Spring のDIコンテナを使用して実装した  | 
Spring Validation  | 
  | 
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 ValidationSpring のDIコンテナを使用した
Bean Validation
10.2.3.1.1.1. Hibernate validatorを使用したBean Validationのテスト¶
Hibernate validatorを使用したBean Validationの単体テストにおいて、作成するファイルを以下に示す。
作成するファイル名  | 
説明  | 
|---|---|
  | 
  | 
  | 
  | 
ここでは、テスト対象の@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メソッドを実行することで、入力チェックエラーの数だけConstrainViolationのSetが返ってくる。
validateメソッドの引数にはFullWidthKatakanaBeanクラスのオブジェクトを指定する。 | 
(3) 
 | 
(2)で取得した 
Setから、エラーが発生したかどうかを確認する。今回はエラーがないため、空の 
Setが返ってくる。 | 
Note
バリデーショングループを使用したテスト
バリデーショングループを設定している場合、入力チェックを行なう際のvalidateメソッド引数に、グループを示す任意のjava.lang.Classオブジェクトを指定することで、指定したグループのValidatorのみ適用して実行できる。
バリデーショングループについては、バリデーションのグループ化を参照されたい。
以下に、バリデーショングループを使用したForm例を示す。
テスト対象の
FullWidthKatakanaTestBean.javapublic 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.javapublic 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の単体テストにおいて、作成するファイルを以下に示す。
作成するファイル名  | 
説明  | 
|---|---|
  | 
Spring のDIコンテナを使用した  | 
  | 
Spring Testを使用して単体テストを行う際に必要な設定を補うための設定ファイル。  | 
作成するファイル名  | 
説明  | 
|---|---|
  | 
Spring のDIコンテナを使用した  | 
  | 
Spring Testを使用して単体テストを行う際に必要な設定を補うための設定ファイル。  | 
Spring のDIコンテナを利用したBean Validationは、org.springframework.validation.beanvalidation.LocalValidatorFactoryBeanからValidatorオブジェクトを生成することでテストすることができる。
@ExistInCodeListを例にテストの実装方法を説明する。@ExistInCodeListについての詳細はコードリストを用いたコード値の入力チェックを参照されたい。テストで使用する設定ファイルに、Validatorオブジェクトを生成するためのLocalValidatorFactoryBeanをBean定義する。
TestContextConfig.java
// (1)
/**
 * Configure {@link LocalValidatorFactoryBean} bean.
 * @return Bean of configured {@link LocalValidatorFactoryBean}
 */
@Bean("validator")
public LocalValidatorFactoryBean localValidatorFactoryBean() {
    return new LocalValidatorFactoryBean();
}
項番  | 
説明  | 
|---|---|
(1) 
 | 
@ExistInCodeListでDIコンテナからコードリストBeanを取得するため、
TestContextConfig.javaでBean定義したLocalValidatorFactoryBeanから生成したValidatorを使う必要がある。 | 
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のテストクラス作成方法を説明する。
ここでは、SampleCodeListConfig.javaまたはsample-codelist.xmlに定義したコードリスト(CL_AIRPORT)に定義していない値を設定し、
インジェクションしたjakarta.validation.Validatorの実装クラスによりバリデーションチェックエラーになることを確認している。
ExistInCodeListTest.java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { SampleEnvConfig.class, SampleInfraConfig.class, SampleCodeListConfig.class,
                TestContextConfig.class })
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"));
    }
}
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)の単体テストにおいて、作成するファイルを以下に示す。
作成するファイル名  | 
説明  | 
|---|---|
  | 
  | 
以下に、テスト対象のクラスを示す。
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) 
 | 
エラーコード、メッセージ内容を検証する。 
 |