Skip to the content.

Command DTO Test Guide — Command DTO 테스트 가이드

Command DTO의 단위 테스트Bean Validation 테스트 가이드입니다.

외부 의존성 없이, 순수 Java 단위 테스트로 빠르게 검증합니다.


1) 테스트 원칙


2) 단위 테스트 (Validation)

기본 생성 테스트

@Tag("unit")
@Tag("adapter-rest")
@Tag("dto")
class CreateOrderApiRequestTest {

    @Test
    void givenValidRequest_whenCreate_thenSuccess() {
        // given
        var request = new CreateOrderApiRequest(
            1L,
            List.of(new OrderItemRequest(1L, 2, 10000L)),
            new AddressRequest("12345", "Seoul", null)
        );

        // when & then
        assertThat(request.customerId()).isEqualTo(1L);
        assertThat(request.items()).hasSize(1);
        assertThat(request.items()).isUnmodifiable();  // ✅ 불변 리스트
    }
}

Compact Constructor 검증 테스트

@Test
void givenInvalidCustomerId_whenCreate_thenThrowException() {
    // when & then
    assertThatThrownBy(() -> new CreateOrderApiRequest(
        -1L,  // ❌ 잘못된 ID (Compact Constructor에서 검증)
        List.of(),
        null
    )).isInstanceOf(IllegalArgumentException.class)
      .hasMessageContaining("유효하지 않은 고객 ID");
}

@Test
void givenNullItems_whenCreate_thenEmptyList() {
    // given & when
    var request = new CreateOrderApiRequest(
        1L,
        null,  // ✅ null → 빈 리스트로 변환
        null
    );

    // then
    assertThat(request.items()).isEmpty();
    assertThat(request.items()).isUnmodifiable();
}

3) Bean Validation 테스트

Validator 설정

@Tag("unit")
@Tag("adapter-rest")
@Tag("dto")
class CreateOrderApiRequestValidationTest {

    private Validator validator;

    @BeforeEach
    void setUp() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }

    @Test
    void givenNullCustomerId_whenValidate_thenViolation() {
        // given
        var request = new CreateOrderApiRequest(
            null,  // ❌ @NotNull 위반
            List.of(),
            null
        );

        // when
        Set<ConstraintViolation<CreateOrderApiRequest>> violations = validator.validate(request);

        // then
        assertThat(violations).hasSize(1);
        assertThat(violations.iterator().next().getMessage())
            .isEqualTo("고객 ID는 필수입니다");
    }

    @Test
    void givenEmptyItems_whenValidate_thenViolation() {
        // given
        var request = new CreateOrderApiRequest(
            1L,
            List.of(),  // ❌ @NotEmpty 위반
            null
        );

        // when
        Set<ConstraintViolation<CreateOrderApiRequest>> violations = validator.validate(request);

        // then
        assertThat(violations).hasSize(1);
        assertThat(violations.iterator().next().getMessage())
            .isEqualTo("주문 항목은 필수입니다");
    }
}

Nested Record Validation 테스트

@Test
void givenInvalidNestedItem_whenValidate_thenViolation() {
    // given
    var invalidItem = new OrderItemRequest(
        null,  // ❌ productId null
        0,     // ❌ quantity < 1
        -1L    // ❌ price < 0
    );
    var request = new CreateOrderApiRequest(
        1L,
        List.of(invalidItem),
        null
    );

    // when
    Set<ConstraintViolation<CreateOrderApiRequest>> violations = validator.validate(request);

    // then
    assertThat(violations).hasSize(3);  // productId, quantity, price
}

4) 패턴별 테스트

날짜 범위 검증 테스트

@Test
void givenInvalidDateRange_whenCreate_thenThrowException() {
    // when & then
    assertThatThrownBy(() -> new OrderSearchApiRequest(
        LocalDate.of(2025, 12, 31),  // endDate
        LocalDate.of(2025, 1, 1)     // startDate (endDate보다 나중)
    )).isInstanceOf(IllegalArgumentException.class)
      .hasMessageContaining("시작 날짜는 종료 날짜보다 이전이어야 합니다");
}

컬렉션 불변성 테스트

@Test
void givenMutableList_whenCreate_thenImmutableList() {
    // given
    List<OrderItemRequest> mutableList = new ArrayList<>();
    mutableList.add(new OrderItemRequest(1L, 1, 10000L));

    // when
    var request = new CreateOrderApiRequest(1L, mutableList, null);

    // then
    assertThatThrownBy(() -> request.items().add(new OrderItemRequest(2L, 1, 20000L)))
        .isInstanceOf(UnsupportedOperationException.class);  // ✅ 불변
}

5) 테스트 조직화

테스트 클래스 구조

test/
└─ adapter-in/rest-api/
   └─ {bc}/dto/command/
      ├─ CreateOrderApiRequestTest.java           # 기본 생성 테스트
      ├─ CreateOrderApiRequestValidationTest.java # Bean Validation 테스트
      ├─ UpdateOrderStatusApiRequestTest.java
      └─ CancelOrderApiRequestTest.java

테스트 네이밍 규칙

테스트 유형 접미사 예시
기본 단위 테스트 *Test CreateOrderApiRequestTest
Bean Validation *ValidationTest CreateOrderApiRequestValidationTest

6) 테스트 커버리지

필수 테스트 항목


7) 테스트 예시 (전체)

@Tag("unit")
@Tag("adapter-rest")
@Tag("dto")
class CreateOrderApiRequestTest {

    private Validator validator;

    @BeforeEach
    void setUp() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }

    @Nested
    @DisplayName("생성 테스트")
    class CreateTest {

        @Test
        @DisplayName("유효한 데이터로 생성 성공")
        void givenValidData_whenCreate_thenSuccess() {
            // given
            var items = List.of(new OrderItemRequest(1L, 2, 10000L));
            var address = new AddressRequest("12345", "Seoul", null);

            // when
            var request = new CreateOrderApiRequest(1L, items, address);

            // then
            assertThat(request.customerId()).isEqualTo(1L);
            assertThat(request.items()).hasSize(1);
            assertThat(request.items()).isUnmodifiable();
        }

        @Test
        @DisplayName("잘못된 customerId면 예외")
        void givenInvalidCustomerId_whenCreate_thenThrow() {
            assertThatThrownBy(() -> new CreateOrderApiRequest(-1L, List.of(), null))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("유효하지 않은 고객 ID");
        }

        @Test
        @DisplayName("null items는 빈 리스트로 변환")
        void givenNullItems_whenCreate_thenEmptyList() {
            var request = new CreateOrderApiRequest(1L, null, null);
            assertThat(request.items()).isEmpty();
        }
    }

    @Nested
    @DisplayName("Bean Validation 테스트")
    class ValidationTest {

        @Test
        @DisplayName("null customerId면 위반")
        void givenNullCustomerId_whenValidate_thenViolation() {
            var request = new CreateOrderApiRequest(null, List.of(), null);
            Set<ConstraintViolation<CreateOrderApiRequest>> violations = validator.validate(request);

            assertThat(violations).hasSize(1);
            assertThat(violations.iterator().next().getMessage())
                .isEqualTo("고객 ID는 필수입니다");
        }

        @Test
        @DisplayName("빈 items면 위반")
        void givenEmptyItems_whenValidate_thenViolation() {
            var request = new CreateOrderApiRequest(1L, List.of(), null);
            Set<ConstraintViolation<CreateOrderApiRequest>> violations = validator.validate(request);

            assertThat(violations).hasSize(1);
            assertThat(violations.iterator().next().getMessage())
                .isEqualTo("주문 항목은 필수입니다");
        }

        @Test
        @DisplayName("잘못된 Nested Record면 위반")
        void givenInvalidNestedItem_whenValidate_thenViolation() {
            var invalidItem = new OrderItemRequest(null, 0, -1L);
            var request = new CreateOrderApiRequest(1L, List.of(invalidItem), null);

            Set<ConstraintViolation<CreateOrderApiRequest>> violations = validator.validate(request);
            assertThat(violations).hasSizeGreaterThanOrEqualTo(3);
        }
    }
}

8) 체크리스트


작성자: Development Team
최종 수정일: 2025-11-13
버전: 1.0.0