agentsclimarketplace

Quarkus testing

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

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

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.

SKILL.md

14.2 KB, ~3.3k tokens by cl100k_base, 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.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. 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.