Quarkus Tdd

affaan-m/ECC/docs/tr/skills/quarkus-tdd

by affaan-mef648e01899ba3e8dc6371642deaaf64b4477775No license275K starsListed Oct 9, 2026Updated Oct 9, 2026Repository updated 4 days ago

Test-driven development for Quarkus 3.x LTS using JUnit 5, Mockito, REST Assured, Camel testing, and JaCoCo. Use when adding features, fixing bugs, or refactoring event-driven services.

Instructions onlySoftware Development
AI-generated overview

Guides test-driven development for Quarkus 3.x services using JUnit 5, Mockito, REST Assured, Camel testing and JaCoCo.

What it does
Provides a TDD workflow and worked Java examples for Quarkus 3.x services, covering unit tests with Mockito, Camel route tests, event service tests, CompletableFuture async tests and REST Assured resource tests. It describes patterns such as @Nested grouping, givenX_whenY_thenZ naming, the AAA structure and interaction verification, and targets 80%+ coverage measured with JaCoCo.
When to use it
Use it when adding features or REST endpoints, fixing bugs or refactoring Quarkus services. It is aimed at testing data access logic, security rules, reactive flows, Apache Camel routes, RabbitMQ event-driven services and async CompletableFuture processing.
Requirements
Instructions only; no scripts are shipped. The described approach assumes a Quarkus 3.x LTS project with JUnit 5, Mockito, REST Assured, Apache Camel testing support and JaCoCo.

Quarkus TDD İş Akışı

80%+ kapsam (unit + integration) ile Quarkus 3.x servisleri için TDD rehberi. Apache Camel ile event-driven mimariler için optimize edilmiştir.

When to Use

  • Yeni özellikler veya REST endpoint'leri
  • Bug düzeltmeleri veya refactoring'ler
  • Veri erişim mantığı, güvenlik kuralları veya reaktif akışlar ekleme
  • Apache Camel route'larını ve event handler'larını test etme
  • RabbitMQ ile event-driven servisleri test etme
  • Koşullu akış mantığını test etme
  • CompletableFuture async işlemlerini doğrulama
  • LogContext yayılımını test etme

How It Works

  1. Önce testleri yazın (başarısız olmalılar)
  2. Geçmek için minimal kod uygulayın
  3. Testleri yeşil tutarken refactor edin
  4. JaCoCo ile kapsamı zorlayın (%80+ hedef)

Examples

@Nested Organizasyonlu Unit Testler

Kapsamlı ve okunabilir testler için bu yapılandırılmış yaklaşımı izleyin:

java
@ExtendWith(MockitoExtension.class)@DisplayName("As2ProcessingService Unit Tests")class As2ProcessingServiceTest {
  @Mock  private InvoiceFlowValidator invoiceFlowValidator;
  @Mock  private EventService eventService;
  @Mock  private DocumentJobService documentJobService;
  @Mock  private BusinessRulesPublisher businessRulesPublisher;
  @Mock  private FileStorageService fileStorageService;
  @InjectMocks  private As2ProcessingService as2ProcessingService;
  private Path testFilePath;  private LogContext testLogContext;  private InvoiceValidationResult validationResult;  private StoredDocumentInfo documentInfo;
  @BeforeEach  void setUp() {    // ARRANGE - Ortak test verisi    testFilePath = Path.of("/tmp/test-invoice.xml");
    testLogContext = new LogContext();    testLogContext.put(As2Constants.STRUCTURE_ID, "STRUCT-001");    testLogContext.put(As2Constants.FILE_NAME, "invoice.xml");    testLogContext.put(As2Constants.AS2_FROM, "PARTNER-001");
    validationResult = new InvoiceValidationResult();    validationResult.setValid(true);    validationResult.setSize(1024L);    validationResult.setDocumentHash("abc123");
    documentInfo = new StoredDocumentInfo();    documentInfo.setPath("s3://bucket/path/invoice.xml");    documentInfo.setSize(1024L);  }
  @Nested  @DisplayName("processFile için testler")  class ProcessFile {
    @Test    @DisplayName("CHORUS olmayan dosyayı tüm validasyonlarla başarıyla işlemeli")    void givenNonChorusFile_whenProcessFile_thenAllValidationsApplied() throws Exception {      // ARRANGE      testLogContext.put(As2Constants.CHORUS_FLOW, "false");      CustomLog.setCurrentContext(testLogContext);
      when(invoiceFlowValidator.validateFlowWithConfig(          eq(testFilePath),          eq(ValidationFlowConfig.allValidations()),          eq(EInvoiceSyntaxFormat.UBL),          any(LogContext.class)))          .thenReturn(validationResult);
      when(invoiceFlowValidator.computeFlowProfile(any(), any()))          .thenReturn(FlowProfile.BASIC);
      when(fileStorageService.uploadOriginalFile(any(), anyLong(), any(), any()))          .thenReturn(CompletableFuture.completedFuture(documentInfo));
      when(documentJobService.createDocumentAndJobEntities(any(), any(), any(), any(), any()))          .thenReturn(new BusinessRulesPayload());
      // ACT      assertDoesNotThrow(() -> as2ProcessingService.processFile(testFilePath));
      // ASSERT      verify(invoiceFlowValidator).validateFlowWithConfig(          eq(testFilePath),          eq(ValidationFlowConfig.allValidations()),          eq(EInvoiceSyntaxFormat.UBL),          any(LogContext.class));
      verify(eventService).createSuccessEvent(any(StoredDocumentInfo.class),          eq("PERSISTENCE_BLOB_EVENT_TYPE"));      verify(eventService).createSuccessEvent(any(BusinessRulesPayload.class),          eq("BUSINESS_RULES_MESSAGE_SENT"));      verify(businessRulesPublisher).publishAsync(any(BusinessRulesPayload.class));    }
    @Test    @DisplayName("CHORUS_FLOW için schematron validasyonu atlanmalı")    void givenChorusFlow_whenProcessFile_thenSchematronBypassed() throws Exception {      // ARRANGE      testLogContext.put(As2Constants.CHORUS_FLOW, "true");      CustomLog.setCurrentContext(testLogContext);
      when(invoiceFlowValidator.validateFlowWithConfig(          eq(testFilePath),          eq(ValidationFlowConfig.xsdOnly()),          eq(EInvoiceSyntaxFormat.UBL),          any(LogContext.class)))          .thenReturn(validationResult);
      when(fileStorageService.uploadOriginalFile(any(), anyLong(), any(), any()))          .thenReturn(CompletableFuture.completedFuture(documentInfo));
      when(documentJobService.createDocumentAndJobEntities(any(), any(), any(),          eq(FlowProfile.EXTENDED_CTC_FR), any()))          .thenReturn(new BusinessRulesPayload());
      // ACT      assertDoesNotThrow(() -> as2ProcessingService.processFile(testFilePath));
      // ASSERT      verify(invoiceFlowValidator).validateFlowWithConfig(          eq(testFilePath),          eq(ValidationFlowConfig.xsdOnly()),          eq(EInvoiceSyntaxFormat.UBL),          any(LogContext.class));
      verify(documentJobService).createDocumentAndJobEntities(          any(), any(), any(),          eq(FlowProfile.EXTENDED_CTC_FR),          any());    }
    @Test    @DisplayName("Dosya yükleme başarısız olduğunda hata eventi oluşturulmalı")    void givenUploadFailure_whenProcessFile_thenErrorEventCreated() throws Exception {      // ARRANGE      testLogContext.put(As2Constants.CHORUS_FLOW, "false");      CustomLog.setCurrentContext(testLogContext);
      when(invoiceFlowValidator.validateFlowWithConfig(any(), any(), any(), any()))          .thenReturn(validationResult);
      when(invoiceFlowValidator.computeFlowProfile(any(), any()))          .thenReturn(FlowProfile.BASIC);
      documentInfo.setPath(""); // Boş path hatayı tetikler      when(fileStorageService.uploadOriginalFile(any(), anyLong(), any(), any()))          .thenReturn(CompletableFuture.completedFuture(documentInfo));
      // ACT & ASSERT      As2ServerProcessingException exception = assertThrows(          As2ServerProcessingException.class,          () -> as2ProcessingService.processFile(testFilePath)      );
      assertThat(exception.getMessage())          .contains("File path is empty after upload");
      verify(eventService).createErrorEvent(          eq(documentInfo),          eq("FILE_UPLOAD_FAILED"),          contains("File path is empty"));
      verify(businessRulesPublisher, never()).publishAsync(any());    }
    @Test    @DisplayName("CompletableFuture.join() başarısızlığı ele alınmalı")    void givenAsyncUploadFailure_whenProcessFile_thenExceptionThrown() throws Exception {      // ARRANGE      testLogContext.put(As2Constants.CHORUS_FLOW, "false");      CustomLog.setCurrentContext(testLogContext);
      when(invoiceFlowValidator.validateFlowWithConfig(any(), any(), any(), any()))          .thenReturn(validationResult);
      when(invoiceFlowValidator.computeFlowProfile(any(), any()))          .thenReturn(FlowProfile.BASIC);
      CompletableFuture<StoredDocumentInfo> failedFuture =          CompletableFuture.failedFuture(new StorageException("S3 connection failed"));      when(fileStorageService.uploadOriginalFile(any(), anyLong(), any(), any()))          .thenReturn(failedFuture);
      // ACT & ASSERT      assertThrows(          CompletionException.class,          () -> as2ProcessingService.processFile(testFilePath)      );    }
    @Test    @DisplayName("Dosya yolu null olduğunda exception fırlatılmalı")    void givenNullFilePath_whenProcessFile_thenThrowsException() {      // ARRANGE      Path nullPath = null;
      // ACT & ASSERT      NullPointerException exception = assertThrows(          NullPointerException.class,          () -> as2ProcessingService.processFile(nullPath)      );
      verify(invoiceFlowValidator, never()).validateFlowWithConfig(any(), any(), any(), any());    }  }}

Temel Test Desenleri

  1. @Nested Sınıflar: Testleri test edilen metoda göre gruplandırın
  2. @DisplayName: Test raporlarında okunabilir açıklamalar sağlayın
  3. İsimlendirme Kuralı: Netlik için givenX_whenY_thenZ
  4. AAA Deseni: Açık // ARRANGE, // ACT, // ASSERT yorumları
  5. @BeforeEach: Tekrarı azaltmak için ortak test verisi kurulumu
  6. assertDoesNotThrow: Exception yakalamadan başarı senaryolarını test edin
  7. assertThrows: AssertJ kullanarak mesaj doğrulamalı exception senaryolarını test edin
  8. Kapsamlı Kapsam: Mutlu yolları, null girdileri, edge case'leri, exception'ları test edin
  9. Etkileşimleri Doğrulama: Metodların doğru çağrıldığından emin olmak için Mockito verify() kullanın
  10. Hiçbir Zaman Doğrulama: Hata senaryolarında metodların ÇAĞRILMADIĞINI sağlamak için never() kullanın

Camel Route Testi

java
@QuarkusTest@DisplayName("Business Rules Camel Route Tests")class BusinessRulesRouteTest {
  @Inject  CamelContext camelContext;
  @Inject  ProducerTemplate producerTemplate;
  @InjectMock  EventService eventService;
  private BusinessRulesPayload testPayload;
  @BeforeEach  void setUp() {    // ARRANGE - Test verisi    testPayload = new BusinessRulesPayload();    testPayload.setDocumentId(1L);    testPayload.setFlowProfile(FlowProfile.BASIC);  }
  @Nested  @DisplayName("business-rules-publisher route için testler")  class BusinessRulesPublisher {
    @Test    @DisplayName("Mesajı başarıyla RabbitMQ'ya yayınlamalı")    void givenValidPayload_whenPublish_thenMessageSentToQueue() throws Exception {      // ARRANGE      MockEndpoint mockRabbitMQ = camelContext.getEndpoint("mock:rabbitmq", MockEndpoint.class);      mockRabbitMQ.expectedMessageCount(1);
      // Test için gerçek endpoint'i mock ile değiştir      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 — .marshal().json() sonrası body JSON String'dir      mockRabbitMQ.assertIsSatisfied(5000);
      assertThat(mockRabbitMQ.getExchanges()).hasSize(1);      String body = mockRabbitMQ.getExchanges().get(0).getIn().getBody(String.class);      assertThat(body).contains("\"documentId\":1");    }
    @Test    @DisplayName("JSON'a marshalling'i ele almalı")    void givenPayload_whenPublish_thenMarshalledToJson() throws Exception {      // ARRANGE      MockEndpoint mockMarshal = new MockEndpoint("mock:marshal");      camelContext.addEndpoint("mock:marshal", mockMarshal);      mockMarshal.expectedMessageCount(1);
      camelContext.getRouteController().stopRoute("business-rules-publisher");      AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {        advice.weaveAddLast().to("mock:marshal");      });      camelContext.getRouteController().startRoute("business-rules-publisher");
      // ACT      producerTemplate.sendBody("direct:business-rules-publisher", testPayload);
      // ASSERT      mockMarshal.assertIsSatisfied(5000);
      String body = mockMarshal.getExchanges().get(0).getIn().getBody(String.class);      assertThat(body).contains("\"documentId\":1");      assertThat(body).contains("\"flowProfile\":\"BASIC\"");    }  }
  @Nested  @DisplayName("document-processing route için testler")  class DocumentProcessing {
    @Test    @DisplayName("Faturayı doğru işlemciye yönlendirmeli")    void givenInvoiceType_whenProcess_thenRoutesToInvoiceProcessor() throws Exception {      // ARRANGE      MockEndpoint mockInvoice = camelContext.getEndpoint("mock:invoice", MockEndpoint.class);      mockInvoice.expectedMessageCount(1);
      camelContext.getRouteController().stopRoute("document-processing");      AdviceWith.adviceWith(camelContext, "document-processing", advice -> {        advice.weaveByToString(".*direct:process-invoice.*").replace().to("mock:invoice");      });      camelContext.getRouteController().startRoute("document-processing");
      // ACT      producerTemplate.sendBodyAndHeader("direct:process-document",          testPayload, "documentType", "INVOICE");
      // ASSERT      mockInvoice.assertIsSatisfied(5000);    }
    @Test    @DisplayName("Validasyon hatalarını zarif şekilde ele almalı")    void givenValidationError_whenProcess_thenRoutesToErrorHandler() throws Exception {      // ARRANGE      MockEndpoint mockError = camelContext.getEndpoint("mock:error", MockEndpoint.class);      mockError.expectedMessageCount(1);
      camelContext.getRouteController().stopRoute("document-processing");      AdviceWith.adviceWith(camelContext, "document-processing", advice -> {        advice.weaveByToString(".*direct:validation-error-handler.*")            .replace().to("mock:error");      });      camelContext.getRouteController().startRoute("document-processing");
      // Error event oluşturma hatasını gerçek EventService API'si üzerinden simüle et      doThrow(new ValidationException("Invalid document"))          .when(eventService)          .createErrorEvent(any(), eq("VALIDATION_ERROR"), anyString());
      // ACT      producerTemplate.sendBody("direct:process-document", testPayload);
      // ASSERT      mockError.assertIsSatisfied(5000);
      Exception exception = mockError.getExchanges().get(0).getException();      assertThat(exception).isInstanceOf(ValidationException.class);      assertThat(exception.getMessage()).contains("Invalid document");    }  }}

Event Service Testi

java
@ExtendWith(MockitoExtension.class)@DisplayName("EventService Unit Tests")class EventServiceTest {
  @Mock  private EventRepository eventRepository;
  @Mock  private ObjectMapper objectMapper;
  @InjectMocks  private EventService eventService;
  private BusinessRulesPayload testPayload;
  @BeforeEach  void setUp() {    // ARRANGE    testPayload = new BusinessRulesPayload();    testPayload.setDocumentId(1L);  }
  @Nested  @DisplayName("createSuccessEvent için testler")  class CreateSuccessEvent {
    @Test    @DisplayName("Doğru niteliklerle başarı eventi oluşturulmalı")    void givenValidPayload_whenCreateSuccessEvent_thenEventPersisted() throws Exception {      // ARRANGE      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.getPayload().equals("{\"documentId\":1}") &&          event.getTimestamp() != null      ));    }
    @Test    @DisplayName("Payload null olduğunda exception fırlatılmalı")    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("createErrorEvent için testler")  class CreateErrorEvent {
    @Test    @DisplayName("Hata mesajıyla hata eventi oluşturulmalı")    void givenError_whenCreateErrorEvent_thenEventPersistedWithMessage() throws Exception {      // ARRANGE      String errorMessage = "Processing failed";      when(objectMapper.writeValueAsString(testPayload)).thenReturn("{\"documentId\":1}");
      // ACT      assertDoesNotThrow(() ->          eventService.createErrorEvent(testPayload, "PROCESSING_ERROR", errorMessage));
      // ASSERT      verify(eventRepository).persist(argThat(event ->          event.getType().equals("PROCESSING_ERROR") &&          event.getStatus() == EventStatus.ERROR &&          event.getErrorMessage().equals(errorMessage) &&          event.getPayload().equals("{\"documentId\":1}")      ));    }
    @ParameterizedTest    @DisplayName("Geçersiz hata mesajları reddedilmeli")    @ValueSource(strings = {"", " "})    void givenBlankErrorMessage_whenCreateErrorEvent_thenThrowsException(String blankMessage) {      // ACT & ASSERT      IllegalArgumentException exception = assertThrows(          IllegalArgumentException.class,          () -> eventService.createErrorEvent(testPayload, "ERROR", blankMessage)      );
      assertThat(exception.getMessage()).contains("Error message cannot be blank");    }  }}

CompletableFuture Testi

java
@ExtendWith(MockitoExtension.class)@DisplayName("FileStorageService Unit Tests")class FileStorageServiceTest {
  @Mock  private S3Client s3Client;
  @Mock  private ExecutorService executorService;
  @InjectMocks  private FileStorageService fileStorageService;
  private InputStream testInputStream;  private LogContext testLogContext;
  @BeforeEach  void setUp() {    // ARRANGE    testInputStream = new ByteArrayInputStream("test content".getBytes());    testLogContext = new LogContext();    testLogContext.put("traceId", "trace-123");  }
  @Nested  @DisplayName("uploadOriginalFile için testler")  class UploadOriginalFile {
    @Test    @DisplayName("Dosyayı başarıyla yüklemeli ve belge bilgisi döndürmeli")    void givenValidFile_whenUpload_thenReturnsDocumentInfo() throws Exception {      // ARRANGE      doAnswer(invocation -> {        ((Runnable) invocation.getArgument(0)).run();        return null;      }).when(executorService).execute(any(Runnable.class));
      when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class)))          .thenReturn(PutObjectResponse.builder().build());
      // ACT      CompletableFuture<StoredDocumentInfo> future =          fileStorageService.uploadOriginalFile(testInputStream, 1024L,              testLogContext, InvoiceFormat.UBL);
      StoredDocumentInfo result = future.join();
      // ASSERT      assertThat(result).isNotNull();      assertThat(result.getPath()).isNotBlank();      assertThat(result.getSize()).isEqualTo(1024L);      assertThat(result.getUploadedAt()).isNotNull();
      verify(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class));    }
    @Test    @DisplayName("S3 yükleme başarısızlığını ele almalı")    void givenS3Failure_whenUpload_thenCompletableFutureFails() {      // ARRANGE      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 unavailable"));
      // ACT      CompletableFuture<StoredDocumentInfo> future =          fileStorageService.uploadOriginalFile(testInputStream, 1024L,              testLogContext, InvoiceFormat.UBL);
      // ASSERT      assertThatThrownBy(() -> future.join())          .isInstanceOf(CompletionException.class)          .hasCauseInstanceOf(StorageException.class)          .hasMessageContaining("S3 unavailable");    }
    @Test    @DisplayName("LogContext'i async işleme yaymalı")    void givenLogContext_whenUpload_thenContextPropagated() throws Exception {      // ARRANGE      AtomicReference<LogContext> capturedContext = new AtomicReference<>();
      doAnswer(invocation -> {        capturedContext.set(CustomLog.getCurrentContext());        ((Runnable) invocation.getArgument(0)).run();        return null;      }).when(executorService).execute(any(Runnable.class));
      // ACT      fileStorageService.uploadOriginalFile(testInputStream, 1024L,          testLogContext, InvoiceFormat.UBL).join();
      // ASSERT      assertThat(capturedContext.get()).isNotNull();      assertThat(capturedContext.get().get("traceId")).isEqualTo("trace-123");    }  }}

Resource Katmanı Testleri (REST Assured)

java
@QuarkusTest@DisplayName("DocumentResource API Tests")class DocumentResourceTest {
  @InjectMock  DocumentService documentService;
  @Nested  @DisplayName("GET /api/documents için testler")  class ListDocuments {
    @Test    @DisplayName("Belge listesini döndürmeli")    void givenDocumentsExist_whenList_thenReturnsOk() {      // ARRANGE      List<Document> documents = List.of(createDocument(1L, "DOC-001"));      when(documentService.list(0, 20)).thenReturn(documents);
      // ACT & ASSERT      given()          .when().get("/api/documents")          .then()          .statusCode(200)          .body("$.size()", is(1))          .body("[0].referenceNumber", equalTo("DOC-001"));    }  }
  @Nested  @DisplayName("POST /api/documents için testler")  class CreateDocument {
    @Test    @DisplayName("Belge oluşturmalı ve 201 döndürmeli")    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": "Test document",                "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("Geçersiz girdi için 400 döndürmeli")    void givenInvalidRequest_whenCreate_thenReturns400() {      // ACT & ASSERT      given()          .contentType(ContentType.JSON)          .body("""              {                "referenceNumber": "",                "description": "Test"              }              """)          .when().post("/api/documents")          .then()          .statusCode(400);    }  }
  private Document createDocument(Long id, String referenceNumber) {    Document document = new Document();    document.setId(id);    document.setReferenceNumber(referenceNumber);    document.setStatus(DocumentStatus.PENDING);    return document;  }}

Gerçek Veritabanıyla Entegrasyon Testleri

java
@QuarkusTest@TestProfile(IntegrationTestProfile.class)@DisplayName("Document Integration Tests")class DocumentIntegrationTest {
  @Test  @Transactional  @DisplayName("Belge oluşturulmalı ve API üzerinden alınabilmeli")  void givenNewDocument_whenCreateAndRetrieve_thenSuccessful() {    // ACT - API üzerinden oluştur    Long id = given()        .contentType(ContentType.JSON)        .body("""            {              "referenceNumber": "INT-001",              "description": "Integration test",              "validUntil": "2030-01-01T00:00:00Z",              "categories": ["test"]            }            """)        .when().post("/api/documents")        .then()        .statusCode(201)        .extract().path("id");
    // ASSERT - API üzerinden al    given()        .when().get("/api/documents/" + id)        .then()        .statusCode(200)        .body("referenceNumber", equalTo("INT-001"));  }}

JaCoCo ile Kapsam

Maven Yapılandırması (Tam)

xml
<plugin>  <groupId>org.jacoco</groupId>  <artifactId>jacoco-maven-plugin</artifactId>  <version>0.8.13</version>  <executions>    <!-- Test yürütmesi için agent'ı hazırla -->    <execution>      <id>prepare-agent</id>      <goals>        <goal>prepare-agent</goal>      </goals>    </execution>
    <!-- Kapsam raporu oluştur -->    <execution>      <id>report</id>      <phase>verify</phase>      <goals>        <goal>report</goal>      </goals>    </execution>
    <!-- Kapsam eşiklerini zorla -->    <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>

Kapsam ile testleri çalıştırın:

bash
mvn clean testmvn jacoco:reportmvn jacoco:check
# Rapor: target/site/jacoco/index.html

Test Bağımlılıkları

xml
<dependencies>    <!-- Quarkus Test -->    <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>
    <!-- Mockito -->    <dependency>        <groupId>org.mockito</groupId>        <artifactId>mockito-core</artifactId>        <scope>test</scope>    </dependency>
    <!-- AssertJ (JUnit assertion'larına tercih edilir) -->    <dependency>        <groupId>org.assertj</groupId>        <artifactId>assertj-core</artifactId>        <version>3.24.2</version>        <scope>test</scope>    </dependency>
    <!-- REST Assured -->    <dependency>        <groupId>io.rest-assured</groupId>        <artifactId>rest-assured</artifactId>        <scope>test</scope>    </dependency>
    <!-- Camel Test -->    <dependency>        <groupId>org.apache.camel.quarkus</groupId>        <artifactId>camel-quarkus-junit5</artifactId>        <scope>test</scope>    </dependency></dependencies>

En İyi Uygulamalar

Test Organizasyonu

  • Testleri test edilen metoda göre gruplandırmak için @Nested sınıflar kullanın
  • Raporlarda görünür okunabilir açıklamalar için @DisplayName kullanın
  • Test metodları için givenX_whenY_thenZ isimlendirme kuralını izleyin
  • Tekrarı azaltmak için ortak test verisi kurulumunda @BeforeEach kullanın

Test Yapısı

  • Açık yorumlarla AAA desenini izleyin (// ARRANGE, // ACT, // ASSERT)
  • Başarı senaryoları için assertDoesNotThrow kullanın
  • Mesaj doğrulamalı exception senaryoları için assertThrows kullanın
  • AssertJ contains() veya isEqualTo() kullanarak exception mesajlarının beklenen değerlerle eşleştiğini doğrulayın

Test Kapsamı

  • Tüm public metodlar için mutlu yolları test edin
  • Null girdi işlemeyi test edin
  • Edge case'leri test edin (boş koleksiyonlar, sınır değerleri, negatif ID'ler, boş string'ler)
  • Exception senaryolarını kapsamlı biçimde test edin
  • Tüm harici bağımlılıkları mock'layın (repository'ler, servisler, Camel endpoint'leri)
  • %80+ satır kapsamı, %70+ branch kapsamı hedefleyin

Assertion'lar

  • Değer kontrolleri için JUnit assertion'ları yerine AssertJ'yi tercih edin (assertThat)
  • Okunabilirlik için akıcı AssertJ API'si kullanın: assertThat(list).hasSize(3).contains(item)
  • Exception'lar için: JUnit assertThrows ile yakalayın, ardından AssertJ ile mesajı doğrulayın
  • Fırlatılmayan başarı yolları için: JUnit assertDoesNotThrow kullanın
  • Koleksiyonlar için: extracting(), filteredOn(), containsExactly()

Entegrasyon Testi

  • Entegrasyon testleri için @QuarkusTest kullanın
  • Quarkus testlerinde bağımlılıkları mock'lamak için @InjectMock kullanın
  • API testi için REST Assured'ı tercih edin
  • Test'e özel yapılandırma için @TestProfile kullanın

Event-Driven Test

  • AdviceWith ve MockEndpoint ile Camel route'larını test edin
  • @CamelQuarkusTest annotasyonu kullanın (bağımsız Camel testleri kullanıyorsanız)
  • Mesaj içeriğini, başlıklarını ve yönlendirme mantığını doğrulayın
  • Hata işleme route'larını ayrı ayrı test edin
  • Unit testlerde harici sistemleri (RabbitMQ, S3, veritabanları) mock'layın

Camel Route Testi

  • Mesaj akışını doğrulamak için MockEndpoint kullanın
  • Test için route'ları değiştirmek üzere AdviceWith kullanın (endpoint'leri mock'larla değiştirin)
  • Mesaj dönüşümünü ve marshalling'i test edin
  • Exception işleme ve dead letter queue'ları test edin

Async İşlem Testi

  • CompletableFuture başarı ve başarısızlık senaryolarını test edin
  • Async tamamlanmayı beklemek için testlerde .join() kullanın
  • CompletableFuture'dan exception yayılımını test edin
  • LogContext yayılımını async işlemlere doğrulayın

Performans

  • Testleri hızlı ve izole tutun
  • Testleri sürekli modda çalıştırın: mvn quarkus:test
  • Girdi varyasyonları için parametreli testler (@ParameterizedTest) kullanın
  • Yeniden kullanılabilir test verisi builder'ları veya factory metodları oluşturun

Quarkus'a Özgü

  • En son LTS sürümünde kalın (Quarkus 3.x)
  • Native derleme uyumluluğunu periyodik olarak test edin
  • Farklı senaryolar için Quarkus test profillerini kullanın
  • Yerel test için Quarkus dev servislerinden yararlanın
  • @MockBean yerine @InjectMock kullanın (Quarkus'a özgü)

Doğrulama En İyi Uygulamaları

  • Mock'lanmış bağımlılıklardaki etkileşimleri her zaman doğrulayın
  • Hata senaryolarında metodların ÇAĞRILMADIĞINI sağlamak için verify(mock, never()) kullanın
  • Karmaşık argüman eşleştirme için argThat() kullanın
  • Önem taşıdığında çağrı sırasını doğrulayın: Mockito'dan InOrder

Source and attribution

Source:affaan-m/ECCindocs/tr/skills/quarkus-tddat commitef648e0

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal