[2023年4月版]Awaitilityで非同期のJUnitテスト

2023/04/12に公開

JUnit で非同期のテストしたい時に

JUnitのテスト書いてて非同期のテストしたいって時に使えるパッケージが Awaitility です。

準備

Maven には以下のように依存関係を追加します。

pom.xml
<dependencies>
    ...
    <dependency>
      <groupId>org.awaitility</groupId>
      <artifactId>awaitility</artifactId>
      <version>4.2.0</version>
      <scope>test</scope>
    </dependency>
    ...
</dependencies>

Gradle には以下のように依存関係を追加します。

build.gradle
dependencies {
    ...
    testImplementation 'org.awaitility:awaitility:4.2.0'
}

テストケースの作成

使い方ですが、自分はちょっと混乱しました。(なので記事書いてますが)

まず、import するべきパッケージですが、ドキュメントには以下のように記載されてます。
必須なのは以下、

  • org.awaitility.Awaitility.*;

で、追加で以下のパッケージも import しとくといいよ~とのことですが…

java.time.Duration.*
java.util.concurrent.TimeUnit.*
org.hamcrest.Matchers.*
org.junit.Assert.*

java.time.Duration.*java.util.concurrent.TimeUnit.* は時間指定の際に必要なので適宜、import するとして、
org.hamcrest.Matchers.*org.junit.Assert.* ですが、これ JUnit4 ではOKですが、JUnit5 では、

import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.jupiter.api.Assertions.*;

になるかと~。
いや、ぶっちゃけJUnit5がアレなんですが。まぁ、いいけどさ…

つづいて、テストケースのサンプルです。
もっとも基本的ですべてを物語るケースが以下です。

    @Test
    public void testAwaitility() {
        await().until(() -> true);
    }

シンプルに "trueになるまで待つ" ってだけなんですが…
untilの引数が評価関数というか Lambda なのがミソです。
デフォルトのタイムアウトは10秒です。

シンプルすぎて混乱したのがここで、true になるまで待つのはその通りですが、戻り値が true であればなんでもOKなので、

    @Test
    public void testAwaitility2() {
        await().until(() -> "test".equals("test"));
    }

こんなんでもOKなんですね。
以下のアサーションと分離するタイプも書けるので上記と混乱してしまいました。

    @Test
    public void testAwaitility3() {
        await().until(() -> "test", equalTo("test"));
    }

この場合は、何かを返す関数はその時の状態を返せばよくて、アサーションが成功するまで待ってくれると、そういうことなんですね。
かならず true を返す関数にしなくてはいけないと思ってしまって意味が分かりませんでした。

続いてAtomic使ってタイマーに書き込ませる処理、ちゃんと非同期してみましょう。

    @Test
    public void testAtomicInteger() {
        var atomic = new AtomicInteger(0);
        var timer = new Timer();
        timer.schedule( new TimerTask() {
            public void run() {
                atomic.set(10);
            }
        } , 5000);
        await().untilAtomic(atomic, equalTo(10));
    }

こんな感じで、遅れて10になるAtomicIntegerもちゃんとアサーションできます。(ドヤぁぁぁぁ!!!)

ついでに、Timerをキャンセルすればちゃんと?失敗します。

    @Test
    public void testAtomicInteger() {
        var atomic = new AtomicInteger(0);
        var timer = new Timer();
        timer.schedule( new TimerTask() {
            public void run() {
                atomic.set(10);
            }
        } , 5000);
        // 実行をキャンセル
        timer.cancel();
        // これはタイムアウトで失敗
        await().untilAtomic(atomic, equalTo(10));
    }

AtomicBooleanはショートカットもあります。

    @Test
    public void testAtomicBoolean() {
        var atomic = new AtomicBoolean(false);
        new Timer().schedule( new TimerTask() {
            public void run() {
                atomic.set(true);
            }
        } , 5000);
        await().untilTrue(atomic);
    }

バッチリですね!
そのほか、詳しい使い方もドキュメントにしっかり記載されているので、よく読んでおきます…

おしまい

さて、今夜はこんな感じです。
なにかと非同期でのテスト、役立つのではないでしょうか。

お疲れさまでした。

ところで TimerTask はラムダ式使えないのかい。。。

Discussion