Testing Quarkus REST Services with @QuarkusTest and RestAssured

Testing Quarkus REST Services with @QuarkusTest and RestAssured

Quarkus has become one of the most compelling Java frameworks for building cloud-native microservices. Its supersonic startup time and low memory footprint make it ideal for containers and serverless deployments. But fast startup means nothing if your services aren't properly tested. This guide covers everything you need to know about testing Quarkus REST services using @QuarkusTest, RestAssured, and the broader Quarkus testing ecosystem.

Why Quarkus Testing Is Different

Quarkus takes an opinionated approach to testing that differs from traditional Spring Boot or Jakarta EE patterns. Rather than spinning up a full application server, Quarkus uses a Dev Mode testing model where the application boots once per test suite (not per test class), keeping test execution fast.

The Quarkus test extension integrates with JUnit 5 and provides specialized annotations that control how the application context is managed during tests.

Setting Up the Testing Dependencies

Add the following to your 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>

If you use Gradle, add to build.gradle:

testImplementation 'io.quarkus:quarkus-junit5'
testImplementation 'io.rest-assured:rest-assured'

Writing Your First @QuarkusTest

The @QuarkusTest annotation is the foundation. It starts the Quarkus application before tests run and tears it down after. Here's a basic REST endpoint and its test:

// The endpoint
@Path("/greetings")
@Produces(MediaType.APPLICATION_JSON)
public class GreetingResource {
    
    @GET
    @Path("/{name}")
    public Greeting greet(@PathParam("name") String name) {
        return new Greeting("Hello, " + name + "!");
    }
}
// The test
@QuarkusTest
class GreetingResourceTest {

    @Test
    void testGreetEndpoint() {
        given()
            .pathParam("name", "World")
        .when()
            .get("/greetings/{name}")
        .then()
            .statusCode(200)
            .body("message", equalTo("Hello, World!"));
    }
}

RestAssured's fluent DSL maps naturally onto HTTP — given() sets up the request, when() specifies the method and path, and then() asserts the response.

Testing POST and Request Bodies

Testing mutations requires constructing request bodies. RestAssured handles JSON serialization cleanly:

@QuarkusTest
class OrderResourceTest {

    @Test
    void testCreateOrder() {
        OrderRequest request = new OrderRequest("product-123", 2);
        
        given()
            .contentType(ContentType.JSON)
            .body(request)
        .when()
            .post("/orders")
        .then()
            .statusCode(201)
            .body("orderId", notNullValue())
            .body("status", equalTo("PENDING"));
    }
    
    @Test
    void testCreateOrderValidationFails() {
        given()
            .contentType(ContentType.JSON)
            .body("{\"quantity\": -1}")
        .when()
            .post("/orders")
        .then()
            .statusCode(400);
    }
}

Mocking Dependencies with @InjectMock

When testing a resource that depends on a service, you don't want to invoke real downstream calls. Quarkus provides @InjectMock to replace CDI beans with Mockito mocks:

@QuarkusTest
class PaymentResourceTest {

    @InjectMock
    PaymentService paymentService;

    @Test
    void testPaymentProcessed() {
        when(paymentService.process(any()))
            .thenReturn(new PaymentResult("TXN-001", "SUCCESS"));
        
        given()
            .contentType(ContentType.JSON)
            .body(new PaymentRequest("100.00", "USD"))
        .when()
            .post("/payments")
        .then()
            .statusCode(200)
            .body("transactionId", equalTo("TXN-001"));
    }
    
    @Test
    void testPaymentFailed() {
        when(paymentService.process(any()))
            .thenThrow(new PaymentException("Insufficient funds"));
        
        given()
            .contentType(ContentType.JSON)
            .body(new PaymentRequest("100.00", "USD"))
        .when()
            .post("/payments")
        .then()
            .statusCode(402);
    }
}

Using @TestHTTPEndpoint for Cleaner Tests

Quarkus provides @TestHTTPEndpoint to bind a test class to a specific resource, eliminating hardcoded paths:

@QuarkusTest
@TestHTTPEndpoint(UserResource.class)
class UserResourceTest {

    @Test
    void testGetUser() {
        given()
            .pathParam("id", "user-42")
        .when()
            .get("/{id}")  // no /users prefix needed
        .then()
            .statusCode(200)
            .body("id", equalTo("user-42"));
    }
}

This approach is more resilient to path changes — if UserResource moves from /users to /api/v1/users, your tests don't need updating.

Integration Testing with Testcontainers

For tests that need a real database, Quarkus integrates with Testcontainers through @QuarkusTestResource:

public class PostgresResource implements QuarkusTestResourceLifecycleManager {

    private static final PostgreSQLContainer<?> POSTGRES = 
        new PostgreSQLContainer<>("postgres:15")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @Override
    public Map<String, String> start() {
        POSTGRES.start();
        return Map.of(
            "quarkus.datasource.jdbc.url", POSTGRES.getJdbcUrl(),
            "quarkus.datasource.username", POSTGRES.getUsername(),
            "quarkus.datasource.password", POSTGRES.getPassword()
        );
    }

    @Override
    public void stop() {
        POSTGRES.stop();
    }
}

@QuarkusTest
@QuarkusTestResource(PostgresResource.class)
class UserRepositoryTest {

    @Inject
    UserRepository userRepository;

    @Test
    @Transactional
    void testSaveAndFindUser() {
        User user = new User("alice@example.com", "Alice");
        userRepository.persist(user);
        
        User found = userRepository.findByEmail("alice@example.com");
        assertThat(found).isNotNull();
        assertThat(found.getName()).isEqualTo("Alice");
    }
}

Quarkus 3.x also supports @WithTestResource directly on fields for more granular control.

Testing Profiles with @QuarkusTestProfile

Different test scenarios often require different configurations. Quarkus test profiles let you customize the application context per test class:

public class MockServicesProfile implements QuarkusTestProfile {
    
    @Override
    public Map<String, String> getConfigOverrides() {
        return Map.of(
            "external.payment.url", "http://localhost:9090/mock",
            "feature.new-checkout", "true"
        );
    }
    
    @Override
    public Set<Class<?>> getEnabledAlternatives() {
        return Set.of(MockEmailService.class);
    }
}

@QuarkusTest
@TestProfile(MockServicesProfile.class)
class CheckoutFeatureTest {
    // Tests run with mock external services and feature flag enabled
}

Native Image Testing with @QuarkusIntegrationTest

When targeting GraalVM native builds, you need a separate test layer that exercises the compiled binary:

@QuarkusIntegrationTest
class NativeGreetingResourceIT extends GreetingResourceTest {
    // Inherits all tests from GreetingResourceTest
    // but runs against the native binary
}

Run native tests with:

./mvnw verify -Pnative

Testing WebSocket Endpoints

Quarkus supports testing WebSocket endpoints with the @QuarkusTest annotation plus the WebSocket client:

@QuarkusTest
class ChatEndpointTest {

    @Test
    void testWebSocketMessage() throws Exception {
        URI uri = URI.create("ws://localhost:8081/chat/room1");
        
        try (Session session = ContainerProvider.getWebSocketContainer()
                .connectToServer(TestClient.class, uri)) {
            
            session.getBasicRemote().sendText("Hello!");
            
            assertThat(TestClient.received)
                .containsExactly("Echo: Hello!");
        }
    }
}

Organizing Tests in Quarkus Projects

Structure your test directories to separate unit tests from integration tests:

src/
  test/java/
    com/example/
      unit/           # Pure unit tests — no @QuarkusTest needed
        OrderCalculatorTest.java
        PriceFormatterTest.java
      integration/    # @QuarkusTest — boots application
        OrderResourceTest.java
        PaymentResourceTest.java
      native/         # @QuarkusIntegrationTest
        OrderResourceIT.java

Unit tests in the unit/ package run without the Quarkus test framework overhead, keeping your fast feedback loop intact for business logic validation.

Continuous Testing with Quarkus Dev Mode

Quarkus's continuous testing mode runs tests automatically as you save files:

./mvnw quarkus:test

In dev mode, press r to run tests, o to toggle output, h for help. The test engine only re-runs tests affected by your changes, giving you near-instant feedback during development.

Connecting Quarkus Tests to Broader QA

Unit and integration tests catch code-level issues. But verifying that your Quarkus microservice actually behaves correctly end-to-end — across authentication, API contracts, and user workflows — requires a higher-level layer.

HelpMeTest lets you write plain-English tests that run against your deployed Quarkus services. You can set up health checks with helpmetest health quarkus-api 30s to monitor your service continuously, and write behavior-driven scenarios without Robot Framework expertise. It integrates directly with your CI pipeline and alerts you when services regress between deployments.

Key Takeaways

  • @QuarkusTest boots the full application once per test suite — keep tests independent but leverage the shared context
  • @InjectMock replaces CDI beans with Mockito mocks without restarting the application
  • @TestHTTPEndpoint binds tests to resource classes, making them resilient to path changes
  • Testcontainers with @QuarkusTestResource provides real database testing without environment coupling
  • @QuarkusIntegrationTest validates native image behavior — critical for GraalVM deployments
  • Continuous testing mode in Dev Mode eliminates the compile-test cycle friction

Quarkus's testing infrastructure is one of its strongest selling points. The fast boot, profile system, and first-class RestAssured integration make it possible to maintain comprehensive test coverage without sacrificing developer velocity.

Read more