affaan-m/ecc1 file

Quarkus Tdd

Desarrollo guiado por pruebas para Quarkus 3.x LTS usando JUnit 5, Mockito, REST Assured, pruebas Camel y JaCoCo. Usar al agregar funcionalidades, corregir bugs o refactorizar servicios orientados a eventos.

Specification
Skill ID
affaan-m/ecc/quarkus-tdd
Publisher
affaan-m
Repository
ecc
Installs
0
Files
1
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

affaan-m/ecc/quarkus-tddInstalls these files
  • SKILL.md

What this skill tells the agent

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

Use this skill in a project

Open any RiverX project, search the Skills panel for affaan-m/ecc/quarkus-tdd, and install it in one click.

Use this skill