Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
affaan-m avatar

Quarkus Tdd

  • 1.3k installs
  • 238k repo stars
  • Updated August 5, 2026
  • affaan-m/ecc

This is a copy of quarkus-tdd by affaan-m - installs and ranking accrue to the original listing.

quarkus-tdd is an AI agent skill that enforces strict test-driven development on Quarkus 3.x event-driven services using JUnit 5, Mockito, REST Assured, Camel tests, and JaCoCo for developers who need 80%+ coverage befor

About

quarkus-tdd is an ECC-origin agent skill for test-driven development on Quarkus 3.x LTS event-driven microservices. The workflow sequences JUnit 5 unit tests, Mockito doubles, REST Assured HTTP checks, Apache Camel route tests, and JaCoCo coverage gates targeting 80%+ combined unit and integration coverage. Reach for quarkus-tdd when adding REST endpoints, fixing bugs, refactoring reactive or Camel-backed services, or validating security and data-access logic in Quarkus projects. The skill is optimized for event-driven architectures where Camel routes and async handlers need the same TDD rigor as synchronous REST layers.

  • 80%+ coverage target enforced with JaCoCo for unit and integration tests
  • Structured @Nested test organization using Mockito, REST Assured, and Camel testing
  • 4-step red-green-refactor workflow: write failing test, implement minimal code, refactor while tests stay green
  • Specialized support for Apache Camel routes, RabbitMQ event handlers, reactive streams, and CompletableFuture async logi
  • Applies to new REST endpoints, data access layers, security rules, and LogContext propagation testing

Quarkus Tdd by the numbers

  • 1,341 all-time installs (skills.sh)
  • +83 installs in the week ending Aug 4, 2026 (Skillselion tracking)
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/affaan-m/ecc --skill quarkus-tdd

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs1.3k
repo stars238k
Last updatedAugust 5, 2026
Repositoryaffaan-m/ecc

How do you TDD Quarkus 3.x Camel services?

Follow strict test-driven development when adding features, fixing bugs, or refactoring Quarkus 3.x event-driven services.

Who is it for?

Java developers building Quarkus 3.x event-driven APIs who need agent-guided TDD with JUnit 5, Mockito, REST Assured, and Apache Camel test coverage.

Skip if: Greenfield Spring Boot projects or Quarkus teams that only need manual integration tests without coverage gates.

When should I use this skill?

User adds Quarkus REST endpoints, refactors Camel routes, or asks for TDD with JUnit 5 and 80% JaCoCo coverage on Quarkus 3.x.

What you get

JUnit 5 test classes, REST Assured integration specs, Camel route tests, and JaCoCo coverage reports at 80%+.

  • JUnit 5 test classes
  • REST Assured specs
  • JaCoCo coverage report

By the numbers

  • Targets 80%+ unit and integration test coverage with JaCoCo on Quarkus 3.x LTS services

Files

SKILL.mdMarkdownGitHub ↗

Flujo de Trabajo TDD en Quarkus

Orientación TDD para servicios Quarkus 3.x con 80%+ de cobertura (unit + integración). Optimizado para arquitecturas orientadas a eventos con Apache Camel.

Cuándo Usar

  • Nuevas funcionalidades o endpoints REST
  • Correcciones de bugs o refactorizaciones
  • Agregar lógica de acceso a datos, reglas de seguridad o streams reactivos
  • Probar rutas Apache Camel y manejadores de eventos
  • Probar servicios orientados a eventos con RabbitMQ
  • Probar lógica de flujo condicional
  • Validar operaciones asíncronas con CompletableFuture
  • Probar propagación de LogContext

Flujo de Trabajo

1. Escribir pruebas primero (deben fallar) 2. Implementar el código mínimo para que pasen 3. Refactorizar con pruebas en verde 4. Exigir cobertura con JaCoCo (objetivo 80%+)

Pruebas Unitarias con Organización @Nested

@ExtendWith(MockitoExtension.class)
@DisplayName("Pruebas Unitarias de OrderService")
class OrderServiceTest {

  @Mock
  private OrderRepository orderRepository;

  @Mock
  private EventService eventService;

  @Mock
  private FulfillmentPublisher fulfillmentPublisher;

  @InjectMocks
  private OrderService orderService;

  private CreateOrderCommand validCommand;

  @BeforeEach
  void setUp() {
    validCommand = new CreateOrderCommand(
        "customer-123",
        List.of(new OrderLine("sku-123", 2))
    );
  }

  @Nested
  @DisplayName("Pruebas para createOrder")
  class CreateOrder {

    @Test
    @DisplayName("Debe persistir orden y publicar evento de fulfillment")
    void givenValidCommand_whenCreateOrder_thenPersistsAndPublishes() {
      // ARRANGE
      doNothing().when(orderRepository).persist(any(Order.class));

      // ACT
      OrderReceipt receipt = orderService.createOrder(validCommand);

      // ASSERT
      assertThat(receipt).isNotNull();
      assertThat(receipt.customerId()).isEqualTo("customer-123");
      verify(orderRepository).persist(any(Order.class));
      verify(fulfillmentPublisher).publishAsync(receipt);
      verify(eventService).createSuccessEvent(receipt, "ORDER_CREATED");
    }

    @Test
    @DisplayName("Debe rechazar customer id vacío")
    void givenMissingCustomerId_whenCreateOrder_thenThrowsBadRequest() {
      // ARRANGE
      CreateOrderCommand invalid = new CreateOrderCommand("", validCommand.lines());

      // ACT & ASSERT
      WebApplicationException exception = assertThrows(
          WebApplicationException.class,
          () -> orderService.createOrder(invalid)
      );

      assertThat(exception.getResponse().getStatus()).isEqualTo(400);
      verify(orderRepository, never()).persist(any(Order.class));
      verify(fulfillmentPublisher, never()).publishAsync(any());
    }

    @Test
    @DisplayName("Debe registrar evento de error cuando falla la persistencia")
    void givenPersistenceFailure_whenCreateOrder_thenRecordsErrorEvent() {
      // ARRANGE
      doThrow(new PersistenceException("base de datos no disponible"))
          .when(orderRepository).persist(any(Order.class));

      // ACT & ASSERT
      PersistenceException exception = assertThrows(
          PersistenceException.class,
          () -> orderService.createOrder(validCommand)
      );

      assertThat(exception.getMessage()).contains("base de datos no disponible");
      verify(eventService).createErrorEvent(
          eq(validCommand),
          eq("ORDER_CREATE_FAILED"),
          contains("base de datos no disponible")
      );
      verify(fulfillmentPublisher, never()).publishAsync(any());
    }
  }
}

Patrones Clave de Prueba

1. Clases @Nested: Agrupar pruebas por método bajo prueba 2. @DisplayName: Proporcionar descripciones legibles para reportes 3. Convención de nombres: givenX_whenY_thenZ para claridad 4. Patrón AAA: Comentarios explícitos // ARRANGE, // ACT, // ASSERT 5. @BeforeEach: Configurar datos de prueba comunes para reducir duplicación 6. assertDoesNotThrow: Probar escenarios exitosos sin capturar excepciones 7. assertThrows: Probar escenarios de excepción con validación de mensajes 8. verify(): Asegurar que los métodos sean llamados correctamente 9. never(): Asegurar que los métodos NO sean llamados en escenarios de error

Pruebas de Rutas Camel

@QuarkusTest
@DisplayName("Pruebas de Ruta Camel Business Rules")
class BusinessRulesRouteTest {

  @Inject
  CamelContext camelContext;

  @Inject
  ProducerTemplate producerTemplate;

  @InjectMock
  EventService eventService;

  @InjectMock
  DocumentValidator documentValidator;

  private BusinessRulesPayload testPayload;

  @BeforeEach
  void setUp() {
    testPayload = new BusinessRulesPayload();
    testPayload.setDocumentId(1L);
    testPayload.setFlowProfile(FlowProfile.BASIC);
  }

  @Nested
  @DisplayName("Pruebas para ruta business-rules-publisher")
  class BusinessRulesPublisher {

    @Test
    @DisplayName("Debe publicar mensaje exitosamente en RabbitMQ")
    void givenValidPayload_whenPublish_thenMessageSentToQueue() throws Exception {
      // ARRANGE
      MockEndpoint mockRabbitMQ = camelContext.getEndpoint("mock:rabbitmq", MockEndpoint.class);
      mockRabbitMQ.expectedMessageCount(1);

      camelContext.getRouteController().stopRoute("business-rules-publisher");
      AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {
        advice.replaceFromWith("direct:business-rules-publisher");
        advice.weaveByToString(".*spring-rabbitmq.*").replace().to("mock:rabbitmq");
      });
      camelContext.getRouteController().startRoute("business-rules-publisher");

      // ACT
      producerTemplate.sendBody("direct:business-rules-publisher", testPayload);

      // ASSERT
      mockRabbitMQ.assertIsSatisfied(5000);

      assertThat(mockRabbitMQ.getExchanges()).hasSize(1);
      String body = mockRabbitMQ.getExchanges().get(0).getIn().getBody(String.class);
      assertThat(body).contains("\"documentId\":1");
    }
  }
}

Pruebas de Servicios de Eventos

@ExtendWith(MockitoExtension.class)
@DisplayName("Pruebas Unitarias de EventService")
class EventServiceTest {

  @Mock
  private EventRepository eventRepository;

  @Mock
  private ObjectMapper objectMapper;

  @InjectMocks
  private EventService eventService;

  @Nested
  @DisplayName("Pruebas para createSuccessEvent")
  class CreateSuccessEvent {

    @Test
    @DisplayName("Debe crear evento de éxito con atributos correctos")
    void givenValidPayload_whenCreateSuccessEvent_thenEventPersisted() throws Exception {
      // ARRANGE
      BusinessRulesPayload testPayload = new BusinessRulesPayload();
      testPayload.setDocumentId(1L);
      when(objectMapper.writeValueAsString(testPayload)).thenReturn("{\"documentId\":1}");

      // ACT
      assertDoesNotThrow(() ->
          eventService.createSuccessEvent(testPayload, "DOCUMENT_PROCESSED"));

      // ASSERT
      verify(eventRepository).persist(argThat(event ->
          event.getType().equals("DOCUMENT_PROCESSED") &&
          event.getStatus() == EventStatus.SUCCESS &&
          event.getTimestamp() != null
      ));
    }

    @Test
    @DisplayName("Debe lanzar excepción cuando el payload es null")
    void givenNullPayload_whenCreateSuccessEvent_thenThrowsException() {
      // ARRANGE
      Object nullPayload = null;

      // ACT & ASSERT
      NullPointerException exception = assertThrows(
          NullPointerException.class,
          () -> eventService.createSuccessEvent(nullPayload, "EVENT_TYPE")
      );

      assertThat(exception.getMessage()).isEqualTo("Payload cannot be null");
      verify(eventRepository, never()).persist(any());
    }
  }

  @Nested
  @DisplayName("Pruebas para createErrorEvent")
  class CreateErrorEvent {

    @ParameterizedTest
    @DisplayName("Debe rechazar mensajes de error inválidos")
    @ValueSource(strings = {"", " "})
    void givenBlankErrorMessage_whenCreateErrorEvent_thenThrowsException(String blankMessage) {
      // ARRANGE
      BusinessRulesPayload testPayload = new BusinessRulesPayload();

      // ACT & ASSERT
      IllegalArgumentException exception = assertThrows(
          IllegalArgumentException.class,
          () -> eventService.createErrorEvent(testPayload, "ERROR", blankMessage)
      );

      assertThat(exception.getMessage()).contains("Error message cannot be blank");
    }
  }
}

Pruebas de CompletableFuture

@ExtendWith(MockitoExtension.class)
class FileStorageServiceTest {

  @Mock
  private S3Client s3Client;

  @Mock
  private ExecutorService executorService;

  @InjectMocks
  private FileStorageService fileStorageService;

  @Test
  @DisplayName("Debe manejar fallo de S3")
  void givenS3Failure_whenUpload_thenCompletableFutureFails() {
    // ARRANGE — ejecutar sincrónicamente para que la excepción se propague
    doAnswer(invocation -> {
      ((Runnable) invocation.getArgument(0)).run();
      return null;
    }).when(executorService).execute(any(Runnable.class));

    when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class)))
        .thenThrow(new StorageException("S3 no disponible"));

    // ACT
    CompletableFuture<StoredDocumentInfo> future =
        fileStorageService.uploadOriginalFile(testInputStream, 1024L,
            testLogContext, InvoiceFormat.UBL);

    // ASSERT
    assertThatThrownBy(() -> future.join())
        .isInstanceOf(CompletionException.class)
        .hasCauseInstanceOf(StorageException.class)
        .hasMessageContaining("S3 no disponible");
  }
}

Pruebas de Capa de Recurso (REST Assured)

@QuarkusTest
@DisplayName("Pruebas de API DocumentResource")
class DocumentResourceTest {

  @InjectMock
  DocumentService documentService;

  @Test
  @DisplayName("Debe crear documento y retornar 201")
  void givenValidRequest_whenCreate_thenReturns201() {
    // ARRANGE
    Document document = createDocument(1L, "DOC-001");
    when(documentService.create(any())).thenReturn(document);

    // ACT & ASSERT
    given()
        .contentType(ContentType.JSON)
        .body("""
            {
              "referenceNumber": "DOC-001",
              "description": "Documento de prueba",
              "validUntil": "2030-01-01T00:00:00Z",
              "categories": ["test"]
            }
            """)
        .when().post("/api/documents")
        .then()
        .statusCode(201)
        .header("Location", containsString("/api/documents/1"))
        .body("referenceNumber", equalTo("DOC-001"));
  }

  @Test
  @DisplayName("Debe retornar 400 para entrada inválida")
  void givenInvalidRequest_whenCreate_thenReturns400() {
    given()
        .contentType(ContentType.JSON)
        .body("""
            {
              "referenceNumber": "",
              "description": "Test"
            }
            """)
        .when().post("/api/documents")
        .then()
        .statusCode(400);
  }
}

Cobertura con JaCoCo

Configuración Maven (Completa)

<plugin>
  <groupId>org.jacoco</groupId>
  <artifactId>jacoco-maven-plugin</artifactId>
  <version>0.8.13</version>
  <executions>
    <execution>
      <id>prepare-agent</id>
      <goals>
        <goal>prepare-agent</goal>
      </goals>
    </execution>
    <execution>
      <id>report</id>
      <phase>verify</phase>
      <goals>
        <goal>report</goal>
      </goals>
    </execution>
    <execution>
      <id>check</id>
      <goals>
        <goal>check</goal>
      </goals>
      <configuration>
        <rules>
          <rule>
            <element>BUNDLE</element>
            <limits>
              <limit>
                <counter>LINE</counter>
                <value>COVEREDRATIO</value>
                <minimum>0.80</minimum>
              </limit>
              <limit>
                <counter>BRANCH</counter>
                <value>COVEREDRATIO</value>
                <minimum>0.70</minimum>
              </limit>
            </limits>
          </rule>
        </rules>
      </configuration>
    </execution>
  </executions>
</plugin>

Ejecutar pruebas con cobertura:

mvn clean test
mvn jacoco:report
mvn jacoco:check

# Reporte en: target/site/jacoco/index.html

Dependencias de Prueba

<dependencies>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-junit5</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-junit5-mockito</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.assertj</groupId>
        <artifactId>assertj-core</artifactId>
        <version>3.24.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.camel.quarkus</groupId>
        <artifactId>camel-quarkus-junit5</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Buenas Prácticas

Organización de Pruebas

  • Usar clases @Nested para agrupar pruebas por método bajo prueba
  • Usar @DisplayName para descripciones legibles en reportes
  • Seguir la convención de nombres givenX_whenY_thenZ
  • Usar @BeforeEach para configuración de datos comunes

Cobertura de Pruebas

  • Probar rutas felices para todos los métodos públicos
  • Probar manejo de entradas null
  • Probar casos borde (colecciones vacías, valores de frontera)
  • Probar escenarios de excepción de forma comprensiva
  • Apuntar a 80%+ de cobertura de líneas, 70%+ de ramas

Aserciones

  • Preferir AssertJ (assertThat) sobre aserciones JUnit para verificar valores
  • Para excepciones: usar JUnit assertThrows para capturar, luego AssertJ para validar
  • Para escenarios exitosos sin excepción: usar JUnit assertDoesNotThrow

Pruebas de Integración

  • Usar @QuarkusTest para pruebas de integración
  • Usar @InjectMock para mockear dependencias en pruebas Quarkus
  • Preferir REST Assured para pruebas de API
  • Usar @TestProfile para configuración específica de prueba

Related skills

How it compares

Pick quarkus-tdd over generic Java TDD skills when the stack is Quarkus 3.x with Apache Camel routes and REST Assured integration tests.

FAQ

What coverage target does quarkus-tdd enforce?

quarkus-tdd targets 80%+ combined unit and integration coverage on Quarkus 3.x services, verified with JaCoCo. The skill sequences JUnit 5, Mockito, REST Assured, and Camel route tests so coverage gates pass before merge.

Which Quarkus testing libraries does quarkus-tdd cover?

quarkus-tdd standardizes on JUnit 5 for unit tests, Mockito for doubles, REST Assured for HTTP integration checks, and dedicated Apache Camel route tests. It applies to Quarkus 3.x LTS event-driven services including reactive streams and security rules.

Testing & QAbackendtestingintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.