QUARKUS RESTAPI Testing
Lightning-Fast Guide: Testing a REST API with Quarkus
Quarkus was built for developer joy, and nowhere is that clearer than in its testing story. With a couple of annotations and a dash of REST Assured, you can spin up a full application (or a native executable!) inside your test and poke at it just like a real client. Below is a compact, copy-ready blog post you can drop into your site.
1. Why bother?
Quarkus boots in milliseconds, so you can run true integration tests—not just unit tests—without the usual “start-the-world” pain. That means you catch wiring bugs, config mishaps, and serialization issues early, while feedback is still cheap.
2. Add the test goodies
<!-- pom.xml -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
That’s all you need—Quarkus bundles JUnit 5 and REST Assured glue for you.
3. A first “hello” test
@Path("/hello")
public class HelloResource {
@GET
public String hello() { return "hello"; }
}
@QuarkusTest
class HelloResourceTest {
@Test
void shouldSayHello() {
given()
.when().get("/hello")
.then().statusCode(200)
.body(is("hello"));
}
}
@QuarkusTest
boots the whole app in JVM mode. REST Assured’s fluent DSL keeps assertions readable for humans and IDE-friendly for machines.
4. Native mode—same test, different binary
Need production-identical startup? Add the @QuarkusIntegrationTest
twin:
@QuarkusIntegrationTest
class HelloNativeIT extends HelloResourceTest {}
Maven goal mvn verify -Pnative
builds a GraalVM binary first, then re-runs the test suite against it—no code duplication required.
5. Mocking external services
-
CDI beans – Replace any
@ApplicationScoped
bean with Mockito
@InjectMock
ExternalService ext; // your mock
Quarkus automatically swaps the real bean for the fake during the test run. quarkus.io
-
Singletons – Add
convertScopes = true
if the class is@Singleton
. Stack Overflow -
Remote REST calls – Use the Quarkus REST client interface plus
@RegisterRestClient
and mock it the same way, or drop in WireMock if you prefer full HTTP mocks.
6. Tuning for flaky networks
If your API streaming or reactive endpoints sometimes need longer, tweak REST Assured time-outs in application.properties
:
quarkus.test.http-client.timeout=45S
No need to write boilerplate code; Quarkus propagates the setting into the embedded HTTP client. Red Hat Documentation
7. Handy extras
Situation | Quarkus feature |
---|---|
Different configs per test class | @TestProfile |
Database starts empty? | quarkus-test-h2 or quarkus-test-containers |
Need to fiddle with CDI at runtime | QuarkusMock.installMockForType() |
8. TL;DR checklist
-
Add
quarkus-junit5
& REST Assured -
Annotate tests with
@QuarkusTest
(or@QuarkusIntegrationTest
for native) -
Mock anything external with
@InjectMock
or WireMock -
Keep tests fast—Quarkus makes millisecond boot times the norm
-
Enjoy red-green-refactor cycles that feel as snappy as plain unit tests, but give you production-grade confidence
Mocking an outbound REST call with WireMock
1️⃣ Using the Quarkus WireMock extension (easiest)
-
Add the extension
# CLI
quarkus ext add io.quarkiverse.wiremock:quarkus-wiremock
or in pom.xml
io.quarkus
quarkus-junit5
test
io.rest-assured
rest-assured
test
The extension spins up WireMock automatically in dev & test mode, assigns it a free port, and exposes that port as quarkus.wiremock.devservices.port
.
2. Point your REST client at WireMock
# application.properties (test profile)
quarkus.rest-client.remote-service.url=http://localhost:${quarkus.wiremock.devservices.port}
3. Write the test
@QuarkusTest
@WireMockInject // provided by the extension
class GreetResourceTest {
@InjectWireMock
WireMockServer wire; // auto-started & auto-stopped
@Test
void shouldReturnMockedGreeting() {
wire.stubFor(WireMock.get("/api/greet")
.willReturn(WireMock.aResponse()
.withStatus(200)
.withBody("Howdy from WireMock")));
given()
.when().get("/hello") // calls your Quarkus endpoint
.then().statusCode(200)
.body(is("Howdy from WireMock"));
}
}
2️⃣ “Plain Java” style (if you don’t want the extension)
<!--{cke_protected}{C}%3C!%2D%2D%20pom.xml%20%2D%2D%3E--> <dependency> <groupid>com.github.tomakehurst</groupid> <artifactid>wiremock-jre8</artifactid> <version>2.35.2</version> <scope>test</scope> </dependency>
@QuarkusTest
class GreetResourceTest {
static WireMockServer wire =
new WireMockServer(WireMockConfiguration.options().dynamicPort());
@BeforeAll
static void start() {
wire.start();
// Override the REST client base URL so your app calls WireMock:
System.setProperty("quarkus.rest-client.remote-service.url",
wire.baseUrl());
}
@AfterAll
static void stop() {
wire.stop();
}
@Test
void shouldReturnMockedGreeting() {
wire.stubFor(get(urlEqualTo("/api/greet"))
.willReturn(aResponse()
.withStatus(200)
.withBody("Howdy from classic WireMock")));
given()
.when().get("/hello")
.then().statusCode(200)
.body(is("Howdy from classic WireMock"));
}
}
This variant gives you full control over the server lifecycle; just remember to stop it in @AfterAll
. WireMock
1️⃣ “Plain Java” WireMock with @AfterAll
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
import org.junit.jupiter.api.*;
@QuarkusTest
class WeatherResourceTest {
private static WireMockServer wire;
@BeforeAll
static void startWireMock() {
wire = new WireMockServer(WireMockConfiguration.options().dynamicPort());
wire.start();
// Tell the Quarkus REST client to hit WireMock instead of the real API
System.setProperty("quarkus.rest-client.weather-service.url", wire.baseUrl());
}
@AfterAll
static void stopWireMock() {
wire.stop();
}
@Test
void shouldReturnMockWeather() {
wire.stubFor(get(urlEqualTo("/api/weather?city=SGP"))
.willReturn(aResponse()
.withStatus(200)
.withBody("{\"temp\":31,\"desc\":\"Humid\"}")));
RestAssured.given()
.when().get("/weather?city=SGP")
.then().statusCode(200)
.body("temp", equalTo(31))
.body("desc", equalTo("Humid"));
}
}
Key points
-
@BeforeAll
spins WireMock up once per class → fastest. -
@AfterAll
always shuts it down, avoiding stray ports when your IDE reruns tests. -
We override the REST-client base URL at runtime so Quarkus calls the stub automatically.
2️⃣ Extension style + JSON stub files
Add the test-only dependency (if you haven’t):
io.quarkiverse.wiremock
quarkus-wiremock
test
Minimal test class
import io.quarkiverse.wiremock.junit5.InjectWireMock;
import io.quarkiverse.wiremock.junit5.WireMockExtension;
import io.quarkiverse.wiremock.test.WireMockFiles;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@QuarkusTest
@WireMockFiles // <-- tells the extension to load JSON mappings
class GreetingResourceIT {
@RegisterExtension
static WireMockExtension wire = WireMockExtension.newInstance()
.options(WireMockExtension.serviceOptions().dynamicPort())
.build();
@InjectWireMock
static com.github.tomakehurst.wiremock.WireMockServer server; // auto-started
@Test
void greetingShouldBeStubbed() {
RestAssured.given()
.when().get("/hello") // Quarkus endpoint internally calls remote /api/greet
.then().statusCode(200)
.body(equalTo("Hola desde WireMock"));
}
}
Where do the JSON files live?
src
└─ test
└─ resources
└─ wiremock ← default folder auto-scanned by @WireMockFiles
├─ mappings
│ └─ greet-mapping.json
└─ __files
└─ greet-body.txt
greet-mapping.json
{
"request": {
"method": "GET",
"url": "/api/greet"
},
"response": {
"status": 200,
"bodyFileName": "greet-body.txt",
"headers": { "Content-Type": "text/plain" }
}
}
Hola desde WireMock
The Quarkus WireMock extension will:
1. Start WireMock on a free port.
2. Publish that port as ${quarkus.wiremock.devservices.port}
(use it in application.properties
).
3. Load every file in wiremock/mappings
automatically—no Java stubbing code needed.
4. Stop the server after all tests—no @AfterAll
required here.
TL;DR
-
Need granular control? Use the plain approach with
@BeforeAll
/@AfterAll
. -
Prefer zero-boilerplate? Drop JSON stubs under
src/test/resources/wiremock
, add@WireMockFiles
, and let the extension handle lifecycle and port wiring.