カスタムバリデーション時のnullについて

エキサイト株式会社 メディア事業部エンジニアの中です。

カスタムバリデーションと@NotEmptyをつけてユニットテストする際、nullを設定すると NullPointerExceptionが発生することがあります。

理由は@NotEmptyと@CustomValidateの2回チェックが通るため、 CustomValidate側の方でnull対策をしておかないといけないからです。

ユースケース

データモデル

@Data
@Accessors(chain = true)
public class TestId {
    @NotEmpty
    @ExciteIdValidate
    private String id;
}

カスタムバリデーション

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {ExciteIdValidator.class})
public @interface ExciteIdValidate {

    String message() default "Idを正しく入力してください";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        ExciteIdValidate[] value();
    }
}
public class ExciteIdValidator implements ConstraintValidator<ExciteIdValidate, String> {

    private final static Pattern PATTERN_NG_WORD = Pattern.compile("ngword");

    @Override
    public void initialize(ExciteIdValidate exciteIdValidate) { }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {

        if(PATTERN_NG_WORD.matcher(value).matches()){
            return false;
        }

        return true;
    }
}

入力例

テストコードです

@ExtendWith(MockitoExtension.class)
class TestIdTest {

    private static Validator validator;

    @BeforeAll
    public static void BeforeAll() {
        validator = Validation
                        .buildDefaultValidatorFactory()
                        .getValidator();
    }

    @Test
    @Description("nullチェック")
    void ngNull() {
        final TestIdForm testId = new TestIdForm()
                .setId(null);
        assertFalse(validator.validate(testId).isEmpty());
    }
}

出力例 エラー部分抜粋しています。

Caused by: java.lang.NullPointerException
    at com.form.validate.TestIdValidator.isValid(TestIdValidator.java:27)
    at com.form.validate.TestIdValidator.isValid(TestIdValidator.java:7)
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateSingleConstraint(ConstraintTree.java:180)
    ... 99 more

改善

当たり前のことですが、nullを考慮して実装すれば問題なく通ります。 returnをfalseにしていますが、trueでも最終的な結果はエラーになります。 @NotEmptyでエラー判定されているからです。

public class ExciteIdValidator implements ConstraintValidator<ExciteIdValidate, String> {

    private final static Pattern PATTERN_NG_WORD = Pattern.compile("ngword");

    @Override
    public void initialize(ExciteIdValidate exciteIdValidate) { }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {

        if(value == null){
            return false;
        }
        if(PATTERN_NG_WORD.matcher(value).matches()){
            return false;
        }

        return true;
    }
}