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.
What this skill does
# 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
```java
@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
```java
@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
```java
@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
BusinessRulRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.