テスト#
jimble にテスト専用のモジュールやアノテーションはありません。
JUnit 5 と java.net.http.HttpClient だけで書きます。
道具はこれだけです。
| やること | 使うもの |
|---|---|
| サーバーを立てる | JimbleServer.start(app, 0)(ポート 0 で空いているところ) |
| 止める | server.stop() |
| HTTP を投げる | JDK の HttpClient |
| 設定を差し替える | Conf.replace(config) / Conf.reload() |
| DB を使う | DBUtil.load(...) / Migration.install() / DBUtil.stop() |
立てて、叩く#
これが基本形です。 実際に Helidon が起動するので、 ルーティング・フィルタ・エラー処理・Content-Type まで通しで確かめられます。
@BeforeAll
static void startServer () {
server = JimbleServer.start(new SampleApp(), 0);
client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
}
@AfterAll
static void stopServer () {
if (server != null) {
server.stop();
}
}
/**
* リクエストを送る
*/
private static HttpResponse<String> request (String method, String path) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://127.0.0.1:" + server.port() + path))
.method(method, HttpRequest.BodyPublishers.noBody())
.timeout(Duration.ofSeconds(10))
.build();
return client.send(request, HttpResponse.BodyHandlers.ofString());
}
ポートを 0 にするのが要点です。固定にすると、
他のテストと同時に走ったときだけ落ちます。
@Test
void hello () throws Exception {
assertEquals("hello", request("GET", "/hello").body());
}
こつ
HttpClient に CookieManager を持たせると、セッション・CSRF・Flash まで通ります。
リダイレクトを検証したいときは followRedirects(HttpClient.Redirect.NEVER) にしてください
(既定では追ってしまい、302 を見られません)。
HTTP を通さずにルータだけ叩く#
サーバーを立てずに Dispatcher を直接呼ぶこともできます。速いぶん、
Helidon 側の挙動(ストリーム送信や圧縮)は確かめられません。
private Fakes.FakeResponseSink dispatch (JimbleApp app, String method, String rawPath) {
Dispatcher dispatcher = new Dispatcher(app);
Fakes.FakeRequestSource source = new Fakes.FakeRequestSource(method, rawPath);
Fakes.FakeResponseSink sink = new Fakes.FakeResponseSink();
try (WebContext context = new WebContext(source, sink)) {
dispatcher.dispatch(context);
return sink;
}
注意
Fakes(上のコードの FakeRequestSource / FakeResponseSink)は
jimble のテストの中にあるもので、公開していません。
アプリから同じことをするなら RequestSource / ResponseSink を自分で実装します。
手間に見合うことは少ないので、まずは上の「立てて、叩く」を使ってください。
DB を使うテスト#
分ける#
DB が要るテストには印を付けて、普段のビルドからは外します。 繋ぎ先が無い環境(CI やクローンした直後)でビルドが赤くなるのを避けるためです。
@Tag("db")
class PostIntegrationTest { ... }
アプリ側の build.gradle.kts にこう足します。
tasks.withType<Test>().configureEach {
useJUnitPlatform { excludeTags("db") }
}
tasks.register<Test>("dbTest") {
group = "verification"
testClassesDirs = sourceSets.test.get().output.classesDirs
classpath = sourceSets.test.get().runtimeClasspath
useJUnitPlatform { includeTags("db") }
// 設定を dbtest に切り替える
systemProperty("jimble.env", "dbtest")
// 前のテストが TRUNCATE した結果を見てしまわないよう、毎回走らせる
outputs.upToDateWhen { false }
}
conf/application.dbtest.conf を置いて、繋ぎ先を書きます。
共通の設定を読むのは1行目の include です(設定)。
include "application.conf"
db {
main {
url = "jdbc:mariadb://127.0.0.1:3306/app_test"
url = ${?TEST_DB_URL}
}
}
立ち上げる#
@BeforeAll
static void startServer () {
Conf.reload();
/*
* <b>本番の入口と同じものを呼ぶ。</b>
* ここでテストだけの用意を書くと、入口が変わったときに<b>テストだけ通る</b>。
*/
Bootstrap.load();
// ポート 0 で空いているところを使う。他のテストと衝突しない
server = JimbleServer.start(new BlogApp(), 0);
client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.cookieHandler(new CookieManager())
.followRedirects(HttpClient.Redirect.NEVER)
.build();
}
順番が決まっています。
Conf.reload()— 環境(jimble.env)は最初に読まれた時点で決まっているので、読み直すMigration.install()— テーブルを作るDBUtil.load(...)— 接続するJimbleServer.start(app, 0)
後始末は @AfterAll の DBUtil.stop()、
各テストの前に TRUNCATE TABLE ... です。
落とし穴
テストごとに自動でロールバックする仕組みはありません。
書いたものは残ります。@BeforeEach で消してください。
並べて走らせない#
注意
DB を使うテストを並列で走らせると、あるテストの TRUNCATE が別のテストの行を消します。
「たまに落ちる」でしか出ないので、原因を追うのに時間を取られます。
Gradle の共有サービス(maxParallelUsages = 1)で直列にするのが確実です。
設定を差し替える#
// 既存の設定に重ねる(ふつうはこちら)
Conf.replace(ConfigFactory
.parseString("upload.max_file_size = 10")
.withFallback(Conf.conf().config()));
落とし穴
Conf.replace はグローバルで、元に戻す口がありません。
差し替えたテストは @AfterEach で Conf.reload() してください。
忘れると、あとから走ったテストだけが落ちます(しかも実行順しだいで変わります)。
jimble 自身のテストを動かす#
./gradlew build # DB 不要のぶん
./gradlew :jimble-db:dbTest # 実 MySQL / MariaDB を使うぶん
./gradlew :jimble-db:pgTest # 同じテストを実 PostgreSQL に流す
dbTest と pgTest はまったく同じテストを走らせます。
違うのは読む設定ファイル(application.dbtest.conf / application.pgtest.conf)だけで、
後者に product = "postgresql" が入っています。
テストのコードは1行も変えていません。
繋ぎ先は環境変数で変えられます。
JIMBLE_TEST_DB_URL="jdbc:mariadb://127.0.0.1:3306/jimble_test" \
JIMBLE_TEST_DB_USER=jimble \
JIMBLE_TEST_DB_PASSWORD=jimble \
./gradlew :jimble-db:dbTest
JIMBLE_TEST_PG_URL="jdbc:postgresql://127.0.0.1:5432/jimble_test" \
JIMBLE_TEST_PG_USER=jimble \
JIMBLE_TEST_PG_PASSWORD=jimble \
./gradlew :jimble-db:pgTest
これは jimble 本体のテスト用です。
examples/ のサンプルは JIMBLE_SAMPLE_DB_* / JIMBLE_SAMPLE_PG_* という別の名前を見ます。
補足
名前を分けているのは、混ぜると事故になるためです。
以前はサンプルも JIMBLE_TEST_* を見ていたので、
この環境変数を立てるとサンプルのマイグレーションが jimble_test に流れ込み、
履歴が混ざってjimble 本体のマイグレーションテストが落ちていました。
落ち方が「フレームワークの不具合」に見えるのが厄介なところでした。
補足
jimble new が作る雛形には、まだテストの足場(JUnit の依存と dbTest)が入っていません。
上のとおり手で足してください。