Java
hamcrest

ラムダ式でHamcrestのMatcherを書けるようにする

JUnit+HamcrestでJavaのテストを実装していると、assertThatメソッドの第2引数をラムダ式で実装したくなる場合があります。ただ、assertThatの第2引数の型はorg.hamcrest.Matcherの為、Java8のラムダ式には対応していません。(Hamcrest 2.1はJava7でビルドされている為)

ラムダ式で値の検証を実装する方法を考えてみました。

テストの実装イメージ

以下のプログラムは、検証対象のメソッドが0以下であるかを確認するテストです。
指定の値より小さい場合のMatcherはhamcrestで提供されていますが、ここではあくまでサンプルということで…。

@Test
public void testSum() {
    // 値が0以下かどうかをテストする
    assertThat(new App().sum(10, -20), is(matches(i -> i <= 0, "negative value")));
}

効果的な利用場面

  • 比較条件が数個の場合。例えば、1,2,3,4のどれかに一致するかを検証する等
  • オブジェクト内のプロパティの比較をequalsメソッド以外のメソッドで実装したい場合
  • 検証方法は単純だが、検証するオブジェクトのクラスが大量にある場合。
    カスタムMatcherのクラスをオブジェクト単位に作成するには手間がかかり過ぎると見込まれる場合。

実現方法

以下のようなカスタムMatcherを実装します。そのカスタムMatcherをstatic importすれば、上記のようなコードを実装できます。

import java.util.function.Predicate;

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

public class LambdaMatcher<T> extends TypeSafeMatcher<T> {

    private Predicate<T> predicate;
    private String message;

    public LambdaMatcher(Predicate<T> predicate, String message) {
        this.predicate = predicate;
        this.message = message;
    }

    @Override
    public void describeTo(Description description) {
        description.appendText(this.message);
    }

    @Override
    protected boolean matchesSafely(T item) {
        return this.predicate.test(item);
    }

    public static <T> Matcher<T> matches(Predicate<T> predicate, String message) {
        return new LambdaMatcher<>(predicate, message);
    }
}

なお、messageをString型で呼出元から受け取る方式にしていますが、Supplier<String>型にするとJUnit5のAssertionsクラスのような使い方もできます。

Hamcrestでラムダ式を使ってテストしたい場合の参考になれば幸いです。

備考

  • 2018/12/31 コメントで指摘があったので、実現方法のコードを修正しました。ありがとうございました。