Testing#
jimble has no test-only module and no test annotations.
You write tests with JUnit 5 and java.net.http.HttpClient, and nothing else.
That is the whole toolbox.
| What to do | What to use |
|---|---|
| Start a server | JimbleServer.start(app, 0) (port 0 takes whatever is free) |
| Stop it | server.stop() |
| Send HTTP | the JDK's HttpClient |
| Swap in configuration | Conf.replace(config) / Conf.reload() |
| Use the DB | DBUtil.load(...) / Migration.install() / DBUtil.stop() |
Start it, hit it#
This is the basic shape. Helidon really starts, so you check routing,
filters, error handling and Content-Type end to end.
@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());
}
Passing 0 for the port is the point. Fix the port and
it fails only when another test happens to run at the same time.
@Test
void hello () throws Exception {
assertEquals("hello", request("GET", "/hello").body());
}
Tip
Give the HttpClient a CookieManager and session, CSRF and flash all work.
To assert on a redirect, set followRedirects(HttpClient.Redirect.NEVER)
(the default follows it, and you never see the 302).
Hitting the router without HTTP#
You can call Dispatcher directly, without starting a server. It is faster, and
in exchange you check nothing on the Helidon side (streamed sends, compression).
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;
}
Careful
Fakes — FakeRequestSource / FakeResponseSink in the code above —
lives inside jimble's own tests and is not published.
To do the same from your application, implement RequestSource / ResponseSink
yourself.
That rarely pays for the effort, so start with "Start it, hit it" above.
Tests that use the DB#
Keep them separate#
Tag the tests that need a DB and leave them out of the everyday build. That keeps the build from going red where there is nothing to connect to — CI, or a fresh clone.
@Tag("db")
class PostIntegrationTest { ... }
Add this to your application's 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") }
// switch the configuration over to dbtest
systemProperty("jimble.env", "dbtest")
// run every time, so it never sees what an earlier test TRUNCATEd
outputs.upToDateWhen { false }
}
Put conf/application.dbtest.conf in place and write the connection there.
The include on the first line is what reads the shared configuration
(Configuration).
include "application.conf"
db {
main {
url = "jdbc:mariadb://127.0.0.1:3306/app_test"
url = ${?TEST_DB_URL}
}
}
Bringing it up#
@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();
}
The order is fixed.
Conf.reload()— the environment (jimble.env) is settled the first time the configuration is read, so read it againMigration.install()— create the tablesDBUtil.load(...)— connectJimbleServer.start(app, 0)
Cleanup is DBUtil.stop() in @AfterAll, and TRUNCATE TABLE ... before each
test.
Trap
There is no per-test automatic rollback.
What you write stays written. Delete it in @BeforeEach.
Do not run them in parallel#
Careful
Run DB tests in parallel and one test's TRUNCATE deletes another test's rows.
It only ever surfaces as "it fails now and then", which costs you a long time
to track down.
A Gradle shared service (maxParallelUsages = 1) serializes them for certain.
Swapping in configuration#
// layer over the existing configuration (this is the usual one)
Conf.replace(ConfigFactory
.parseString("upload.max_file_size = 10")
.withFallback(Conf.conf().config()));
Trap
Conf.replace is global, and there is no way back.
A test that swaps configuration in has to call Conf.reload() in @AfterEach.
Forget it and only the tests that run after it fail — and which ones those
are changes with the execution order.
Running jimble's own tests#
./gradlew build # the ones that need no DB
./gradlew :jimble-db:dbTest # the ones that use a real MySQL / MariaDB
./gradlew :jimble-db:pgTest # the same tests against a real PostgreSQL
dbTest and pgTest run exactly the same tests.
The only difference is which configuration file they read
(application.dbtest.conf / application.pgtest.conf), and the latter carries
product = "postgresql".
Not one line of the test code changes.
You can point them somewhere else with environment variables.
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
These are for jimble's own tests.
The samples under examples/ read different names: JIMBLE_SAMPLE_DB_* / JIMBLE_SAMPLE_PG_*.
Note
The names are separate because mixing them causes real damage.
The samples used to read JIMBLE_TEST_* too, so setting these variables sent
the samples' migrations into jimble_test, mixing up the history and
failing jimble's own migration tests — in a way that looks like a framework bug.
Note
The skeleton jimble new creates does not carry the test scaffolding yet
(the JUnit dependency and dbTest).
Add it by hand, as above.