agentsclimarketplace

Quarkus testing

Skill kinhluan/rules-quarkus-skills/.agent-skills/quarkus-testing

🤖 Complete AI expert ecosystem for Modern Java, Quarkus & Bazel development ☕️⚡️ Coverage for Vert.x, GraalVM, Maven/Gradle migration, and more 🚀

Install
npx -y skills add kinhluan/rules-quarkus-skills --skill quarkus-testing

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Comprehensive Quarkus testing expertise covering @QuarkusTest, @InjectMock, Testcontainers, Dev Services, and native image testing. Use for all Quarkus testing questions.

SKILL.md

14.2 KB, as published. Nobody here has run it

quarkus-testing

Keyword: quarkus-test | Platforms: gemini,claude,codex

Quarkus Testing Expert Skill - Deep knowledge of testing strategies for the Quarkus ecosystem.

Core Mandates

  • Test at the Right Level: Use @QuarkusTest for integration, @QuarkusUnitTest for extension testing, @QuarkusIntegrationTest for native/artifact verification.
  • Mock External Dependencies: Use @InjectMock for CDI beans, WireMock/Testcontainers for external services.
  • Dev Services First: Leverage zero-config Dev Services for databases, Kafka, Redis in dev/test.
  • Test Data Isolation: Each test must be independent - use @Transactional rollback or @TestTransaction.
  • Fast Feedback: Keep @QuarkusTest suite fast; move slow tests to @QuarkusIntegrationTest.

@QuarkusTest Fundamentals

Basic Integration Test

@QuarkusTest
class UserResourceTest {

    @Test
    void shouldListUsers() {
        given()
            .when().get("/api/users")
            .then()
            .statusCode(200)
            .body("$.size()", greaterThanOrEqualTo(0));
    }

    @Test
    void shouldCreateUser() {
        given()
            .contentType(ContentType.JSON)
            .body("""
                {"name": "John Doe", "email": "[email protected]"}
                """)
            .when().post("/api/users")
            .then()
            .statusCode(201)
            .header("Location", containsString("/api/users/"));
    }

    @Test
    void shouldReturn404ForMissingUser() {
        given()
            .when().get("/api/users/99999")
            .then()
            .statusCode(404)
            .body("message", containsString("not found"));
    }
}

Testing with Authentication

@QuarkusTest
class SecuredResourceTest {

    @Test
    void shouldAllowAuthenticatedAccess() {
        given()
            .auth().oauth2(getAccessToken("user"))
            .when().get("/api/profile")
            .then()
            .statusCode(200);
    }

    @Test
    void shouldRejectUnauthorizedAccess() {
        given()
            .when().get("/api/profile")
            .then()
            .statusCode(401);
    }

    @Test
    @TestSecurity(authorizationEnabled = false)
    void shouldBypassAuthForTesting() {
        given()
            .when().get("/api/profile")
            .then()
            .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin", roles = {"admin", "user"})
    void shouldTestWithSpecificRoles() {
        given()
            .when().get("/api/admin/dashboard")
            .then()
            .statusCode(200);
    }
}

@InjectMock & @InjectSpy

Mocking CDI Beans

@QuarkusTest
class OrderServiceTest {

    @InjectMock
    PaymentGateway paymentGateway;

    @InjectMock
    NotificationService notificationService;

    @Inject
    OrderService orderService;

    @Test
    void shouldProcessOrderSuccessfully() {
        when(paymentGateway.charge(any(), any()))
            .thenReturn(Uni.createFrom().item(new PaymentResult("SUCCESS", "txn-123")));

        Order result = orderService.createOrder(new CreateOrderRequest("user-1", BigDecimal.TEN))
            .await().atMost(Duration.ofSeconds(5));

        assertThat(result.status()).isEqualTo(OrderStatus.CONFIRMED);
        verify(paymentGateway).charge(any(), eq(BigDecimal.TEN));
        verify(notificationService).sendOrderConfirmation(result);
    }

    @Test
    void shouldHandlePaymentFailure() {
        when(paymentGateway.charge(any(), any()))
            .thenReturn(Uni.createFrom().failure(new PaymentException("Card declined")));

        assertThatThrownBy(() ->
            orderService.createOrder(new CreateOrderRequest("user-1", BigDecimal.TEN))
                .await().atMost(Duration.ofSeconds(5))
        ).hasCauseInstanceOf(PaymentException.class);

        verify(notificationService, never()).sendOrderConfirmation(any());
    }

    @Test
    void shouldRetryFailedPayment() {
        when(paymentGateway.charge(any(), any()))
            .thenReturn(Uni.createFrom().failure(new PaymentException("Timeout")))
            .thenReturn(Uni.createFrom().item(new PaymentResult("SUCCESS", "txn-456")));

        Order result = orderService.createOrder(new CreateOrderRequest("user-1", BigDecimal.TEN))
            .await().atMost(Duration.ofSeconds(10));

        assertThat(result.status()).isEqualTo(OrderStatus.CONFIRMED);
        verify(paymentGateway, times(2)).charge(any(), any());
    }
}

Spying on Beans

@QuarkusTest
class UserServiceTest {

    @InjectSpy
    UserRepository userRepository;

    @Inject
    UserService userService;

    @Test
    void shouldCacheUserLookup() {
        userService.findById(1L);
        userService.findById(1L);

        verify(userRepository, times(1)).findById(1L);
    }
}

Mockito Argument Matchers with Panache

@QuarkusTest
class PanacheMockingTest {

    @InjectMock
    UserRepository userRepository;

    @Test
    void shouldMockPanacheRepository() {
        User mockUser = new User();
        mockUser.name = "Mocked";
        mockUser.email = "[email protected]";

        PanacheMock.mock(User.class);
        when(User.findById(1L)).thenReturn(mockUser);
        when(User.findByEmail("[email protected]"))
            .thenReturn(Optional.of(mockUser));
        when(User.listAll()).thenReturn(List.of(mockUser));

        // Test your service that uses User.findById(...)
    }
}

Dev Services (Zero-Config Testing)

Database Dev Services

@QuarkusTest
class UserRepositoryTest {

    @Inject
    UserRepository userRepository;

    @Test
    @Transactional
    void shouldPersistAndFindUser() {
        User user = new User();
        user.name = "Alice";
        user.email = "[email protected]";
        userRepository.persist(user);

        Optional<User> found = userRepository.findByEmail("[email protected]");
        assertThat(found).isPresent();
        assertThat(found.get().name).isEqualTo("Alice");
    }
}
// PostgreSQL container auto-starts via Dev Services - zero configuration needed

Custom Dev Services Configuration

# application.properties (test profile)
%test.quarkus.datasource.devservices.image-name=postgres:16-alpine
%test.quarkus.datasource.devservices.port=5432
%test.quarkus.datasource.devservices.db-name=testdb

# Kafka Dev Services
%test.quarkus.kafka.devservices.enabled=true
%test.quarkus.kafka.devservices.image-name=redpandadata/redpanda:latest

# Redis Dev Services
%test.quarkus.redis.devservices.enabled=true
%test.quarkus.redis.devservices.image-name=redis:7-alpine

Multiple Datasources

@QuarkusTest
class MultiDatasourceTest {

    @Inject
    @Named("users")
    AgroalDataSource usersDataSource;

    @Inject
    @Named("analytics")
    AgroalDataSource analyticsDataSource;

    @Test
    void shouldConnectToBothDatabases() {
        assertThat(usersDataSource).isNotNull();
        assertThat(analyticsDataSource).isNotNull();
    }
}

Testcontainers Integration

Custom Testcontainers

@QuarkusTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ExternalServiceIntegrationTest {

    @Container
    static GenericContainer<?> wireMock = new GenericContainer<>("wiremock/wiremock:3.5.2")
        .withExposedPorts(8080)
        .waitingFor(Wait.forHttp("/__admin/mappings").forStatusCode(200));

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine")
        .withDatabaseName("testdb")
        .withUsername("test")
        .withPassword("test");

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("external.api.url",
            () -> "http://localhost:" + wireMock.getMappedPort(8080));
        registry.add("quarkus.datasource.jdbc.url", postgres::getJdbcUrl);
        registry.add("quarkus.datasource.username", postgres::getUsername);
        registry.add("quarkus.datasource.password", postgres::getPassword);
    }

    @Test
    void shouldCallExternalService() {
        // Test code using WireMock and PostgreSQL
    }
}

Shared Containers for Performance

public class SharedPostgresResource
    implements QuarkusTestResourceLifecycleManager {

    static PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>("postgres:16-alpine");

    @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() {
        // Don't stop - shared across tests
    }

    public static PostgreSQLContainer<?> getContainer() {
        return postgres;
    }
}

// Usage
@QuarkusTest
@QuarkusTestResource(SharedPostgresResource.class)
class FastIntegrationTest {
    // Tests reuse the same PostgreSQL container
}

Transactional Testing

Automatic Rollback

@QuarkusTest
class TransactionalTest {

    @Inject
    UserRepository userRepository;

    @Test
    @Transactional
    void shouldNotPersistAfterTest() {
        User user = new User();
        user.name = "Temp";
        userRepository.persist(user);

        assertThat(userRepository.count()).isEqualTo(1);
    }  // Automatically rolled back after test

    @Test
    @Transactional
    void shouldSeeCleanDatabase() {
        // Previous test data is gone due to rollback
        assertThat(userRepository.count()).isEqualTo(0);
    }
}

@TestTransaction (Alternative)

@Test
@TestTransaction
void shouldUseTestTransaction() {
    // Same as @Transactional but semantically clearer
    // Always rolls back after test
}

Manual Transaction Control

@Test
void shouldControlTransactionManually() {
    QuarkusTransaction.begin();
    try {
        userRepository.persist(newUser);
        QuarkusTransaction.commit();
    } catch (Exception e) {
        QuarkusTransaction.rollback();
        throw e;
    }
}

Native Image Testing

@QuarkusIntegrationTest

@QuarkusIntegrationTest
class NativeUserResourceIT {

    @Test
    void shouldWorkInNativeMode() {
        given()
            .when().get("/api/users")
            .then()
            .statusCode(200);
    }
}
// Runs against the native binary or container image

Conditional Native Testing

@QuarkusTest
class UserResourceTest {

    @Test
    @DisabledOnNativeImage  // Skip in native mode
    void shouldTestDevOnlyFeature() {
        // This test only runs in JVM mode
    }

    @Test
    @EnabledOnNativeImage  // Only run in native mode
    void shouldTestNativeOnlyFeature() {
        // This test only runs in native mode
    }
}

REST Assured Patterns

Custom Object Mapping

@QuarkusTest
class AdvancedRestAssuredTest {

    @Test
    void shouldDeserializeResponse() {
        User user = given()
            .when().get("/api/users/1")
            .then()
            .statusCode(200)
            .extract().as(User.class);

        assertThat(user.name()).isEqualTo("Alice");
    }

    @Test
    void shouldValidateJsonSchema() {
        given()
            .when().get("/api/users/1")
            .then()
            .body("id", notNullValue())
            .body("name", not(emptyString()))
            .body("email", matchesPattern(".*@.*\\..*"));
    }

    @Test
    void shouldTestWithQueryParams() {
        given()
            .queryParam("page", 0)
            .queryParam("size", 10)
            .queryParam("sort", "name,asc")
            .when().get("/api/users")
            .then()
            .statusCode(200)
            .body("$.size()", lessThanOrEqualTo(10));
    }
}

Testing Best Practices

Test Pyramid for Quarkus

    /\
   /  \  E2E Tests (@QuarkusIntegrationTest)
  /----\    ~5% of tests
 /      \
/--------\ Integration Tests (@QuarkusTest)
/          \   ~20% of tests
/------------\ Unit Tests (plain JUnit + Mockito)
/              \   ~75% of tests

Test Naming Convention

// Pattern: should<ExpectedBehavior>When<Condition>
@Test
void shouldReturn404WhenUserNotFound() { }

@Test
void shouldSendNotificationWhenOrderCreated() { }

@Test
void shouldRollbackTransactionWhenPaymentFails() { }

Test Data Builders

public class UserBuilder {
    private String name = "Default User";
    private String email = "[email protected]";
    private Status status = Status.ACTIVE;

    public UserBuilder withName(String name) {
        this.name = name;
        return this;
    }

    public UserBuilder withEmail(String email) {
        this.email = email;
        return this;
    }

    public User build() {
        User user = new User();
        user.name = name;
        user.email = email;
        user.status = status;
        return user;
    }
}

// Usage in tests
User user = new UserBuilder()
    .withName("Alice")
    .withEmail("[email protected]")
    .build();

Troubleshooting

Test Hangs or Times Out

Cause: Mutiny Uni/Multi not subscribed or awaited
Fix: Always call .await().atMost(Duration) in tests

Dev Services Not Starting

Cause: Docker not running or insufficient resources
Fix: Check Docker daemon, increase Docker memory limit

@InjectMock Not Working

Cause: Bean not discovered by CDI
Fix: Ensure bean has @ApplicationScoped or @Singleton

References

Skill Interoperability

The quarkus-testing skill extends:

  • quarkus-expert ⚡: Testing patterns for Quarkus applications.
  • java-expert ☕: JUnit 5 and Mockito fundamentals.
  • graalvm-expert 🚀: Native image testing with @QuarkusIntegrationTest.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.