Quarkus Tdd

affaan-m/ECC/pi/core/skills/quarkus-tdd

作者 affaan-mef648e01899ba3e8dc6371642deaaf64b4477775無授權條款275K 個星標收錄於 2026年10月9日更新於 2026年10月9日儲存庫4 天前更新

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.

AI 產生的概覽

指導 Quarkus 3.x 服務的測試驅動開發,使用 JUnit 5、Mockito、REST Assured、Camel 測試與 JaCoCo。

功能
此技能為 Quarkus 3.x 服務提供 TDD 工作流程:先撰寫會失敗的測試,再實作最小可用程式碼,在測試通過後重構,並以 JaCoCo 強制達到 80% 以上覆蓋率。它提供多個完整範例,涵蓋以 @Nested 組織並搭配 Mockito 模擬的單元測試、使用 AdviceWith 與 MockEndpoint 的 Camel 路由測試、事件服務測試、CompletableFuture 與 LogContext 傳遞測試、REST Assured 資源層測試,以及連接真實資料庫的整合測試。它也列出所需的 Maven 測試相依套件與 JaCoCo 外掛設定。
適用情境
適用於新增功能或 REST 端點、修正錯誤或重構 Quarkus 服務時,尤其是採用 Apache Camel、RabbitMQ、反應式串流或非同步 CompletableFuture 作業的事件驅動服務。也適合新增資料存取邏輯、安全規則或條件流程邏輯且需要測試覆蓋的情況。
執行需求
需要一個 Quarkus 3.x LTS 的 Java 專案,使用 Maven,並包含測試相依套件:quarkus-junit5、quarkus-junit5-mockito、mockito-core、assertj-core、rest-assured、camel-quarkus-junit5,以及用於覆蓋率的 JaCoCo。不附帶指令碼,僅為說明文件與程式碼範例。

Quarkus TDD Workflow

TDD guidance for Quarkus 3.x services with 80%+ coverage (unit + integration). Optimized for event-driven architectures with Apache Camel.

When to Use

  • New features or REST endpoints
  • Bug fixes or refactors
  • Adding data access logic, security rules, or reactive streams
  • Testing Apache Camel routes and event handlers
  • Testing event-driven services with RabbitMQ
  • Testing conditional flow logic
  • Validating CompletableFuture async operations
  • Testing LogContext propagation

Workflow

  1. Write tests first (they should fail)
  2. Implement minimal code to pass
  3. Refactor with tests green
  4. Enforce coverage with JaCoCo (80%+ target)

Unit Tests with @Nested Organization

Follow this structured approach for comprehensive, readable tests:

java
@ExtendWith(MockitoExtension.class)@DisplayName("OrderService Unit Tests")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("Tests for createOrder")  class CreateOrder {
    @Test    @DisplayName("Should persist order and publish fulfillment event")    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("Should reject missing customer id")    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("Should record error event when persistence fails")    void givenPersistenceFailure_whenCreateOrder_thenRecordsErrorEvent() {      // ARRANGE      doThrow(new PersistenceException("database unavailable"))          .when(orderRepository).persist(any(Order.class));
      // ACT & ASSERT      PersistenceException exception = assertThrows(          PersistenceException.class,          () -> orderService.createOrder(validCommand)      );
      assertThat(exception.getMessage()).contains("database unavailable");      verify(eventService).createErrorEvent(          eq(validCommand),          eq("ORDER_CREATE_FAILED"),          contains("database unavailable")      );      verify(fulfillmentPublisher, never()).publishAsync(any());    }
    @Test    @DisplayName("Should reject null commands")    void givenNullCommand_whenCreateOrder_thenThrowsNullPointerException() {      // ACT & ASSERT      assertThrows(          NullPointerException.class,          () -> orderService.createOrder(null)      );
      verify(orderRepository, never()).persist(any(Order.class));    }  }}

Key Testing Patterns

  1. @Nested Classes: Group tests by method being tested
  2. @DisplayName: Provide readable test descriptions for test reports
  3. Naming Convention: givenX_whenY_thenZ for clarity
  4. AAA Pattern: Explicit // ARRANGE, // ACT, // ASSERT comments
  5. @BeforeEach: Setup common test data to reduce duplication
  6. assertDoesNotThrow: Test success scenarios without catching exceptions
  7. assertThrows: Test exception scenarios with message validation using AssertJ
  8. Comprehensive Coverage: Test happy paths, null inputs, edge cases, exceptions
  9. Verify Interactions: Use Mockito verify() to ensure methods are called correctly
  10. Never Verify: Use never() to ensure methods are NOT called in error scenarios

Testing Camel Routes

java
@QuarkusTest@DisplayName("Business Rules Camel Route Tests")class BusinessRulesRouteTest {
  @Inject  CamelContext camelContext;
  @Inject  ProducerTemplate producerTemplate;
  @InjectMock  EventService eventService;
  @InjectMock  DocumentValidator documentValidator;
  private BusinessRulesPayload testPayload;
  @BeforeEach  void setUp() {    // ARRANGE - Test data    testPayload = new BusinessRulesPayload();    testPayload.setDocumentId(1L);    testPayload.setFlowProfile(FlowProfile.BASIC);  }
  @Nested  @DisplayName("Tests for business-rules-publisher route")  class BusinessRulesPublisher {
    @Test    @DisplayName("Should successfully publish message to RabbitMQ")    void givenValidPayload_whenPublish_thenMessageSentToQueue() throws Exception {      // ARRANGE      MockEndpoint mockRabbitMQ = camelContext.getEndpoint("mock:rabbitmq", MockEndpoint.class);      mockRabbitMQ.expectedMessageCount(1);
      // Replace real endpoint with mock for testing      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 — body is a JSON String after .marshal().json(JsonLibrary.Jackson)      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("Should handle marshalling to JSON")    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("Tests for document-processing route")  class DocumentProcessing {
    @Test    @DisplayName("Should route invoice to correct processor")    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("Should handle validation errors gracefully")    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");
      // Mock validator bean to throw exception      when(documentValidator.validate(any())).thenThrow(new ValidationException("Invalid document"));
      // 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");    }  }}

Testing Event Services

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("Tests for createSuccessEvent")  class CreateSuccessEvent {
    @Test    @DisplayName("Should create success event with correct attributes")    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("Should throw exception when payload is 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("Tests for createErrorEvent")  class CreateErrorEvent {
    @Test    @DisplayName("Should create error event with error message")    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("Should reject invalid error messages")    @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");    }  }}

Testing CompletableFuture

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("Tests for uploadOriginalFile")  class UploadOriginalFile {
    @Test    @DisplayName("Should successfully upload file and return document info")    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("Should handle S3 upload failure")    void givenS3Failure_whenUpload_thenCompletableFutureFails() {      // ARRANGE — run synchronously so exception propagates through the future      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("Should propagate LogContext to async operation")    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 Layer Tests (REST Assured)

java
@QuarkusTest@DisplayName("DocumentResource API Tests")class DocumentResourceTest {
  @InjectMock  DocumentService documentService;
  @Nested  @DisplayName("Tests for GET /api/documents")  class ListDocuments {
    @Test    @DisplayName("Should return list of documents")    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("Tests for POST /api/documents")  class CreateDocument {
    @Test    @DisplayName("Should create document and return 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": "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("Should return 400 for invalid input")    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;  }}

Integration Tests with Real Database

java
@QuarkusTest@TestProfile(IntegrationTestProfile.class)@DisplayName("Document Integration Tests")class DocumentIntegrationTest {
  @Test  @Transactional  @DisplayName("Should create and retrieve document via API")  void givenNewDocument_whenCreateAndRetrieve_thenSuccessful() {    // ACT - Create via API    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 - Retrieve via API    given()        .when().get("/api/documents/" + id)        .then()        .statusCode(200)        .body("referenceNumber", equalTo("INT-001"));  }}

Coverage with JaCoCo

Maven Configuration (Complete)

xml
<plugin>  <groupId>org.jacoco</groupId>  <artifactId>jacoco-maven-plugin</artifactId>  <version>0.8.13</version>  <executions>    <!-- Prepare agent for test execution -->    <execution>      <id>prepare-agent</id>      <goals>        <goal>prepare-agent</goal>      </goals>    </execution>
    <!-- Generate coverage report -->    <execution>      <id>report</id>      <phase>verify</phase>      <goals>        <goal>report</goal>      </goals>    </execution>
    <!-- Enforce coverage thresholds -->    <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>

Run tests with coverage:

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

Test Dependencies

xml
<dependencies>    <!-- Quarkus Testing -->    <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 (preferred over JUnit assertions) -->    <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 Testing -->    <dependency>        <groupId>org.apache.camel.quarkus</groupId>        <artifactId>camel-quarkus-junit5</artifactId>        <scope>test</scope>    </dependency></dependencies>

Best Practices

Test Organization

  • Use @Nested classes to group tests by method being tested
  • Use @DisplayName for readable test descriptions visible in reports
  • Follow givenX_whenY_thenZ naming convention for test methods
  • Use @BeforeEach for common test data setup to reduce duplication

Test Structure

  • Follow AAA pattern with explicit comments (// ARRANGE, // ACT, // ASSERT)
  • Use assertDoesNotThrow for success scenarios
  • Use assertThrows for exception scenarios with message validation
  • Verify exception messages match expected values using AssertJ contains() or isEqualTo()

Test Coverage

  • Test happy paths for all public methods
  • Test null input handling
  • Test edge cases (empty collections, boundary values, negative IDs, blank strings)
  • Test exception scenarios comprehensively
  • Mock all external dependencies (repositories, services, Camel endpoints)
  • Aim for 80%+ line coverage, 70%+ branch coverage

Assertions

  • Prefer AssertJ (assertThat) over JUnit assertions for value checks
  • Use fluent AssertJ API for readability: assertThat(list).hasSize(3).contains(item)
  • For exceptions: use JUnit assertThrows to capture, then AssertJ to validate the message
  • For non-throwing success paths: use JUnit assertDoesNotThrow
  • For collections: extracting(), filteredOn(), containsExactly()

Testing Integration

  • Use @QuarkusTest for integration tests
  • Use @InjectMock to mock dependencies in Quarkus tests
  • Prefer REST Assured for API testing
  • Use @TestProfile for test-specific configuration

Event-Driven Testing

  • Test Camel routes with AdviceWith and MockEndpoint
  • Use @CamelQuarkusTest annotation (if using standalone Camel tests)
  • Verify message content, headers, and routing logic
  • Test error handling routes separately
  • Mock external systems (RabbitMQ, S3, databases) in unit tests

Camel Route Testing

  • Use MockEndpoint for asserting message flow
  • Use AdviceWith to modify routes for testing (replace endpoints with mocks)
  • Test message transformation and marshalling
  • Test exception handling and dead letter queues

Testing Async Operations

  • Test CompletableFuture success and failure scenarios
  • Use .join() in tests to wait for async completion
  • Test exception propagation from CompletableFuture
  • Verify LogContext propagation to async operations

Performance

  • Keep tests fast and isolated
  • Run tests in continuous mode: mvn quarkus:test
  • Use parameterized tests (@ParameterizedTest) for input variations
  • Build reusable test data builders or factory methods

Quarkus-Specific

  • Stay on latest LTS version (Quarkus 3.x)
  • Test native compilation compatibility periodically
  • Use Quarkus test profiles for different scenarios
  • Leverage Quarkus dev services for local testing
  • Use @InjectMock instead of @MockBean (Quarkus-specific)

Verification Best Practices

  • Always verify interactions on mocked dependencies
  • Use verify(mock, never()) to ensure methods are NOT called in error scenarios
  • Use argThat() for complex argument matching
  • Verify the order of calls when it matters: InOrder from Mockito

來源與署名

來源:affaan-m/ECC位於pi/core/skills/quarkus-tdd提交ef648e0

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架