Christopher Anabo
Christopher Anabo
Senior Tech Lead
Christopher Anabo

Notes

QUARKUS RESTAPI Testing

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

  1. Add quarkus-junit5 & REST Assured

  2. Annotate tests with @QuarkusTest (or @QuarkusIntegrationTest for native)

  3. Mock anything external with @InjectMock or WireMock

  4. Keep tests fast—Quarkus makes millisecond boot times the norm

  5. 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)

  1. 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.

 

GITHUB