ひよっこ。

I want to…

Spring3(というかJSR-303)でBeanValidationのテスト

Posted by hikaruworld : 2011 1月 15

Spring3(というかJSR-303)でBeanValidationのテスト

Spring3にはJSR-303のBeanValidationの機能が実装されています。
これのテストの仕方について調べていました。

対象のコード

例えば以下のようなBeanがあるとします。

public class User {
  private int userId;
  private String userName;
  
  // getter/setter
}

これに、以下のような制約を加えたいとします。

userId

  • 1以上の数字
  • 9桁以内

userName

  • 20文字以内
  • 必須項目

BeanValidation実装後の修正コード

必要なアノテーションを設定すると以下の通りになります。

public class User {
  @Min(value=1)
  @Max(value=999999999)
  private int userId;
  
  @Length(max=20)
  @NotNull
  private String userName;
  
  // getter/setter
}

テストケース

テストケースは以下のようになります。
一部のテストケースだけ抜粋しています。

// Springではなくjavax側のValidatorをimport
import javax.validation.Validator;

public class UserTest {
  /** 検証対象のクラス */
  public User user;
  
  /** Validatorの実行クラス */
  private static Validator validator;
  /** 実行結果格納用 */
  private Set<ConstraintViolation<User>> violations;
  
  /**
   * validatorのインスタンスをFactoryから取得
   */
  @BeforeClass
  public static void beforeClass() {
    validator = Validation.buildDefaultValidatorFactory().getValidator();
  }

  /** 検証対象のクラスを初期化 */
  @Before
  public void before() {
    this.user = new User();
  }

  //---------------------------------------------------------------------------------------------
  //
  // Validationのテスト
  //
  //---------------------------------------------------------------------------------------------
  
  @Test
  public void userIdの最小値の検証_0を許さない() throws Exception {
    this.user.setUserId(0);
    
    this.violations = validator.validate(this.user);
    
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("value", 1L);
    assertThat(this.violations, hasItems(
    		IsViolation.<User>hasViolation("userId", "{javax.validation.constraints.Min.message}", map ))
    );
  }
}

キモは、以下の部分で、validateを実行した結果、
エラーがあった場合にviolationsに格納されます。

Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<User>> violations = validator.validate(this.user);

IsViolation.hasViolationは、
テスト結果がviolationsに格納されてテストしづらいので、
Matcher用のクラスを実装した任意のクラスです。

ちなみにIsViolationクラスは以下の通りです。

package com.wordpress.prepro.controller.utils;

import java.util.HashMap;
import java.util.Map;

import javax.validation.ConstraintViolation;
import javax.validation.Path;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.hamcrest.TypeSafeMatcher;

/**
 * アノテーションフィールドを用いたバリデーションで利用されるMatcherクラス.
 * 未テスト
 * 
 * <h1>利用方法</h1>
 * 以下のようなクラスがあった場合
 * <pre>
 * pubic class User {
 *      @Length(max = "3")
 *      private String title;
 * }
 * </pre>
 * 以下のようにユニットテスト側で実行時に利用される。
 * <pre>
 * User user = new User();
 * user.setTitle("hoge");
 * 
 * Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
 * Set&lt;ConstraintViolation&lt;User&gt;&gt; violations = validator.validate(user);
 * 
 * assertThat(violations, hasItem(IsAnnotationValidate.&lt;User&gt;hasViolation(
 *  "title", "{org.hibernate.validator.constraints.Length.message}")
 * )));
 * </pre>
 *
 * @author morikawa
 * @version 0.0.1 
 *
 * @param <T> アノテーションが付与されているBean
 */
public class IsViolation<T> extends TypeSafeMatcher<ConstraintViolation<T>> {
  
  /** 検証対象フィールド */
  private String propertyField;
  /**
  * エラーメッセージ
  * ローカライズされていないkeyを検証する
  */
  private String messageTemplate;
  /** アノテーションのフィールド検証用 */
  private Map<String, Object> annotationValue;
  
  /**
  * コンストラクタ
  * @param propertyField 検証対象フィールド
  * @param messageTemplate エラーメッセージ
  * @param annotationValue アノテーションに設定された値。ValueのObjectはkeyに依存する
  */
  public IsViolation(String propertyField, String messageTemplate, Map<String, Object> annotationValue) {
    this.propertyField = propertyField;
    this.messageTemplate = messageTemplate;
    this.annotationValue = annotationValue;
  }
  
  /**
  * {@inheritDoc}
  * @see org.hamcrest.SelfDescribing#describeTo(org.hamcrest.Description)
  */
  public void describeTo(Description description) {
    description.appendText("a annotation validate ")
        .appendText("PropertyPath:")
        .appendValue(this.propertyField)
        .appendText(" ")
        .appendText("MessageTempate:")
        .appendValue(this.messageTemplate)
        .appendText(" ")
        .appendText("annotationValue:")
        .appendValue(this.annotationValue);
  }
  
  /**
   * {@inheritDoc}
   * @see org.hamcrest.TypeSafeMatcher#matchesSafely(java.lang.Object)
   */
  @Override
  protected boolean matchesSafely(ConstraintViolation<T> item) {
    if (item == null) {
      return false;
    }
    // PropertyPath
    if (item.getPropertyPath() == null) {
      return false;
    }
    boolean msgField = false;
    for (Path.Node node : item.getPropertyPath()) {
      if (node.getName().equals(this.propertyField)) {
        msgField = true;
        break;
      }
    }
    
    // messageTemplate
    boolean msgTemp = this.messageTemplate.equals(item.getMessageTemplate());
    
    // annotation
    Map<String, Object> atrMap = item.getConstraintDescriptor().getAttributes();
  
    boolean atr = true;;
    for (Map.Entry<String, Object> entry : this.annotationValue.entrySet()) {
      if (atrMap.containsKey(entry.getKey())) {
        if (Matchers.is(entry.getValue()).matches(atrMap.get(entry.getKey()))) {
          continue;
        }
        atr = false;
        break;
      }
      atr = false;
      break;
    }
    return msgField && msgTemp && atr;
  }

  /**
   * 
   * @param <T> 検証対象のPOJO
   * @param propertyField 検証対象フィールド
   * @param messageTemplate エラーメッセージ
   * @param annotationValue アノテーションに設定された値。ValueのObjectはkeyに依存する
   * @return {@link IsViolation}のインスタンス
   * @see IsViolation
   */
  public static <T> Matcher<ConstraintViolation<T>> hasViolation(String propertyField, String messageTemplate, Map<String, Object> annotationValue) {
    return new IsViolation<T>(propertyField, messageTemplate, annotationValue);
  }

  /**
   * Annotationプロパティ検証の省略版
   * @see IsViolation#hasValidate(String, String, String, Map)
   */
  public static <T> Matcher<ConstraintViolation<T>> hasViolation(String propertyField, String messageTemplate) {
    return new IsViolation<T>(propertyField, messageTemplate, new HashMap<String, Object>());
  }	
}

以上です。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

 
%d人のブロガーが「いいね」をつけました。