Skip to the content.

Mapper Test Guide — Mapper 테스트 가이드

Mapper의 단위 테스트 가이드입니다.

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


1) 테스트 원칙


2) 단위 테스트 (변환 검증)

API Request → Command 변환 테스트

@Tag("unit")
@Tag("adapter-rest")
@Tag("mapper")
class OrderApiMapperTest {

    private OrderApiMapper mapper;

    @BeforeEach
    void setUp() {
        mapper = new OrderApiMapper();
    }

    @Test
    @DisplayName("CreateOrderApiRequest → CreateOrderCommand 변환 성공")
    void givenApiRequest_whenToCommand_thenSuccess() {
        // given
        var items = List.of(
            new CreateOrderApiRequest.OrderItem(1L, 2, 10000L),
            new CreateOrderApiRequest.OrderItem(2L, 1, 20000L)
        );
        var apiRequest = new CreateOrderApiRequest(
            100L,
            items,
            new CreateOrderApiRequest.Address("12345", "Seoul", null)
        );

        // when
        CreateOrderCommand command = mapper.toCommand(apiRequest);

        // then
        assertThat(command.customerId()).isEqualTo(100L);
        assertThat(command.items()).hasSize(2);
        assertThat(command.items().get(0).productId()).isEqualTo(1L);
        assertThat(command.items().get(0).quantity()).isEqualTo(2);
        assertThat(command.deliveryAddress().zipCode()).isEqualTo("12345");
    }
}

API Request → Query 변환 테스트

@Test
@DisplayName("OrderSearchApiRequest → SearchOrdersQuery 변환 성공 (Offset)")
void givenOffsetRequest_whenToQuery_thenOffsetQuery() {
    // given
    var apiRequest = new OrderSearchApiRequest(
        100L,
        "PLACED",
        LocalDate.of(2025, 1, 1),
        LocalDate.of(2025, 12, 31),
        null,  // cursor = null → Offset 페이징
        0,     // page
        20,    // size
        "createdAt",
        "DESC"
    );

    // when
    SearchOrdersQuery query = mapper.toQuery(apiRequest);

    // then
    assertThat(query.paginationType()).isEqualTo(PaginationType.OFFSET);
    assertThat(query.customerId()).isEqualTo(100L);
    assertThat(query.page()).isEqualTo(0);
    assertThat(query.size()).isEqualTo(20);
}

@Test
@DisplayName("OrderSearchApiRequest → SearchOrdersQuery 변환 성공 (Cursor)")
void givenCursorRequest_whenToQuery_thenCursorQuery() {
    // given
    var apiRequest = new OrderSearchApiRequest(
        100L,
        "PLACED",
        null,
        null,
        "cursor123",  // cursor 있음 → Cursor 페이징
        null,
        20,
        "createdAt",
        "DESC"
    );

    // when
    SearchOrdersQuery query = mapper.toQuery(apiRequest);

    // then
    assertThat(query.paginationType()).isEqualTo(PaginationType.CURSOR);
    assertThat(query.cursor()).isEqualTo("cursor123");
    assertThat(query.size()).isEqualTo(20);
}

ID → Query 변환 테스트

@Test
@DisplayName("ID → GetOrderQuery 변환 성공")
void givenId_whenToQuery_thenSuccess() {
    // given
    Long orderId = 123L;

    // when
    GetOrderQuery query = mapper.toQuery(orderId);

    // then
    assertThat(query.id()).isEqualTo(123L);
}

3) Application Response → API Response 변환 테스트

기본 Response 변환 테스트

@Test
@DisplayName("OrderResponse → OrderApiResponse 변환 성공")
void givenAppResponse_whenToApiResponse_thenSuccess() {
    // given
    var appResponse = new OrderResponse(
        1L,
        100L,
        BigDecimal.valueOf(30000),
        "PLACED",
        LocalDateTime.of(2025, 1, 1, 10, 0, 0)
    );

    // when
    OrderApiResponse apiResponse = mapper.toApiResponse(appResponse);

    // then
    assertThat(apiResponse.id()).isEqualTo(1L);
    assertThat(apiResponse.customerId()).isEqualTo(100L);
    assertThat(apiResponse.totalAmount()).isEqualByComparingTo(BigDecimal.valueOf(30000));
    assertThat(apiResponse.status()).isEqualTo("PLACED");
}

Detail Response 변환 테스트

@Test
@DisplayName("OrderDetailResponse → OrderDetailApiResponse 변환 성공")
void givenDetailAppResponse_whenToDetailApiResponse_thenSuccess() {
    // given
    var customer = new OrderDetailResponse.CustomerInfo(100L, "홍길동", "hong@example.com");
    var items = List.of(
        new OrderDetailResponse.LineItem(1L, "상품A", 2, BigDecimal.valueOf(10000)),
        new OrderDetailResponse.LineItem(2L, "상품B", 1, BigDecimal.valueOf(20000))
    );
    var appDetailResponse = new OrderDetailResponse(
        1L,
        customer,
        items,
        BigDecimal.valueOf(40000),
        "PLACED",
        LocalDateTime.of(2025, 1, 1, 10, 0, 0)
    );

    // when
    OrderDetailApiResponse apiResponse = mapper.toDetailApiResponse(appDetailResponse);

    // then
    assertThat(apiResponse.id()).isEqualTo(1L);
    assertThat(apiResponse.customer().name()).isEqualTo("홍길동");
    assertThat(apiResponse.lineItems()).hasSize(2);
    assertThat(apiResponse.lineItems().get(0).productName()).isEqualTo("상품A");
}

Page/Slice Response 변환 테스트

@Test
@DisplayName("PageResponse → OrderPageApiResponse 변환 성공")
void givenPageResponse_whenToPageApiResponse_thenSuccess() {
    // given
    var content = List.of(
        new OrderDetailResponse(1L, null, List.of(), BigDecimal.ZERO, "PLACED", null),
        new OrderDetailResponse(2L, null, List.of(), BigDecimal.ZERO, "PLACED", null)
    );
    var appPageResponse = new PageResponse<>(content, 0, 20, 100L);

    // when
    OrderPageApiResponse apiResponse = mapper.toPageApiResponse(appPageResponse);

    // then
    assertThat(apiResponse.content()).hasSize(2);
    assertThat(apiResponse.page()).isEqualTo(0);
    assertThat(apiResponse.size()).isEqualTo(20);
    assertThat(apiResponse.totalElements()).isEqualTo(100L);
}

@Test
@DisplayName("SliceResponse → OrderSliceApiResponse 변환 성공")
void givenSliceResponse_whenToSliceApiResponse_thenSuccess() {
    // given
    var content = List.of(
        new OrderDetailResponse(1L, null, List.of(), BigDecimal.ZERO, "PLACED", null),
        new OrderDetailResponse(2L, null, List.of(), BigDecimal.ZERO, "PLACED", null)
    );
    var appSliceResponse = new SliceResponse<>(content, "cursor123", true);

    // when
    OrderSliceApiResponse apiResponse = mapper.toSliceApiResponse(appSliceResponse);

    // then
    assertThat(apiResponse.content()).hasSize(2);
    assertThat(apiResponse.nextCursor()).isEqualTo("cursor123");
    assertThat(apiResponse.hasNext()).isTrue();
}

4) 의존성 주입 테스트 (MessageSource 등)

MessageSource 주입 테스트

@Tag("unit")
@Tag("adapter-rest")
@Tag("mapper")
class OrderApiMapperWithDependencyTest {

    private OrderApiMapper mapper;
    private MessageSource messageSource;

    @BeforeEach
    void setUp() {
        messageSource = mock(MessageSource.class);
        mapper = new OrderApiMapper(messageSource);
    }

    @Test
    @DisplayName("MessageSource를 사용한 상태 변환 테스트")
    void givenAppResponse_whenToApiResponseWithI18n_thenSuccess() {
        // given
        var appResponse = new OrderResponse(1L, 100L, BigDecimal.ZERO, "PLACED", null);

        when(messageSource.getMessage(
            eq("order.status.PLACED"),
            any(),
            any(Locale.class)
        )).thenReturn("주문완료");

        // when
        OrderApiResponse apiResponse = mapper.toApiResponse(appResponse);

        // then
        assertThat(apiResponse.statusLabel()).isEqualTo("주문완료");
        verify(messageSource).getMessage(eq("order.status.PLACED"), any(), any(Locale.class));
    }
}

5) Edge Case 테스트

Null 처리 테스트

@Test
@DisplayName("Null 필드가 있는 경우 처리")
void givenNullFields_whenToCommand_thenHandleGracefully() {
    // given
    var apiRequest = new CreateOrderApiRequest(
        100L,
        null,  // items = null
        null   // address = null
    );

    // when
    CreateOrderCommand command = mapper.toCommand(apiRequest);

    // then
    assertThat(command.customerId()).isEqualTo(100L);
    assertThat(command.items()).isEmpty();  // null → 빈 리스트
    assertThat(command.deliveryAddress()).isNull();
}

빈 컬렉션 처리 테스트

@Test
@DisplayName("빈 컬렉션 변환")
void givenEmptyCollection_whenToCommand_thenEmptyList() {
    // given
    var apiRequest = new CreateOrderApiRequest(
        100L,
        List.of(),  // 빈 리스트
        null
    );

    // when
    CreateOrderCommand command = mapper.toCommand(apiRequest);

    // then
    assertThat(command.items()).isEmpty();
}

6) 테스트 조직화

테스트 클래스 구조

test/
└─ adapter-in/rest-api/
   └─ {bc}/mapper/
      ├─ OrderApiMapperTest.java                    # 기본 변환 테스트
      ├─ OrderApiMapperWithDependencyTest.java      # 의존성 주입 테스트
      └─ OrderApiMapperEdgeCaseTest.java            # Edge Case 테스트

@Nested 활용 (권장)

@Tag("unit")
@Tag("adapter-rest")
@Tag("mapper")
class OrderApiMapperTest {

    private OrderApiMapper mapper;

    @BeforeEach
    void setUp() {
        mapper = new OrderApiMapper();
    }

    @Nested
    @DisplayName("API Request → Command 변환")
    class ToCommandTest {

        @Test
        @DisplayName("CreateOrderApiRequest → CreateOrderCommand 변환")
        void givenApiRequest_whenToCommand_thenSuccess() {
            // ...
        }

        @Test
        @DisplayName("Null items는 빈 리스트로 변환")
        void givenNullItems_whenToCommand_thenEmptyList() {
            // ...
        }
    }

    @Nested
    @DisplayName("API Request → Query 변환")
    class ToQueryTest {

        @Test
        @DisplayName("Offset 페이징 Query 변환")
        void givenOffsetRequest_whenToQuery_thenOffsetQuery() {
            // ...
        }

        @Test
        @DisplayName("Cursor 페이징 Query 변환")
        void givenCursorRequest_whenToQuery_thenCursorQuery() {
            // ...
        }
    }

    @Nested
    @DisplayName("Application Response → API Response 변환")
    class ToApiResponseTest {

        @Test
        @DisplayName("OrderResponse → OrderApiResponse 변환")
        void givenAppResponse_whenToApiResponse_thenSuccess() {
            // ...
        }

        @Test
        @DisplayName("OrderDetailResponse → OrderDetailApiResponse 변환")
        void givenDetailResponse_whenToDetailApiResponse_thenSuccess() {
            // ...
        }
    }
}

7) 테스트 커버리지

필수 테스트 항목


8) 테스트 예시 (전체)

@Tag("unit")
@Tag("adapter-rest")
@Tag("mapper")
class OrderApiMapperTest {

    private OrderApiMapper mapper;

    @BeforeEach
    void setUp() {
        mapper = new OrderApiMapper();
    }

    @Nested
    @DisplayName("API Request → Command 변환")
    class ToCommandTest {

        @Test
        @DisplayName("CreateOrderApiRequest → CreateOrderCommand 변환 성공")
        void givenValidRequest_whenToCommand_thenSuccess() {
            // given
            var items = List.of(
                new CreateOrderApiRequest.OrderItem(1L, 2, 10000L)
            );
            var apiRequest = new CreateOrderApiRequest(
                100L,
                items,
                new CreateOrderApiRequest.Address("12345", "Seoul", null)
            );

            // when
            CreateOrderCommand command = mapper.toCommand(apiRequest);

            // then
            assertThat(command.customerId()).isEqualTo(100L);
            assertThat(command.items()).hasSize(1);
            assertThat(command.items().get(0).productId()).isEqualTo(1L);
        }

        @Test
        @DisplayName("Null items는 빈 리스트로 변환")
        void givenNullItems_whenToCommand_thenEmptyList() {
            // given
            var apiRequest = new CreateOrderApiRequest(100L, null, null);

            // when
            CreateOrderCommand command = mapper.toCommand(apiRequest);

            // then
            assertThat(command.items()).isEmpty();
        }
    }

    @Nested
    @DisplayName("API Request → Query 변환")
    class ToQueryTest {

        @Test
        @DisplayName("Offset 페이징 Query 변환")
        void givenOffsetRequest_whenToQuery_thenOffsetQuery() {
            // given
            var apiRequest = new OrderSearchApiRequest(
                100L, "PLACED", null, null,
                null,  // cursor = null
                0, 20, "createdAt", "DESC"
            );

            // when
            SearchOrdersQuery query = mapper.toQuery(apiRequest);

            // then
            assertThat(query.paginationType()).isEqualTo(PaginationType.OFFSET);
            assertThat(query.page()).isEqualTo(0);
        }

        @Test
        @DisplayName("Cursor 페이징 Query 변환")
        void givenCursorRequest_whenToQuery_thenCursorQuery() {
            // given
            var apiRequest = new OrderSearchApiRequest(
                100L, "PLACED", null, null,
                "cursor123",  // cursor 있음
                null, 20, "createdAt", "DESC"
            );

            // when
            SearchOrdersQuery query = mapper.toQuery(apiRequest);

            // then
            assertThat(query.paginationType()).isEqualTo(PaginationType.CURSOR);
            assertThat(query.cursor()).isEqualTo("cursor123");
        }

        @Test
        @DisplayName("ID → GetOrderQuery 변환")
        void givenId_whenToQuery_thenSuccess() {
            // when
            GetOrderQuery query = mapper.toQuery(123L);

            // then
            assertThat(query.id()).isEqualTo(123L);
        }
    }

    @Nested
    @DisplayName("Application Response → API Response 변환")
    class ToApiResponseTest {

        @Test
        @DisplayName("OrderResponse → OrderApiResponse 변환")
        void givenAppResponse_whenToApiResponse_thenSuccess() {
            // given
            var appResponse = new OrderResponse(
                1L, 100L, BigDecimal.valueOf(30000), "PLACED",
                LocalDateTime.of(2025, 1, 1, 10, 0, 0)
            );

            // when
            OrderApiResponse apiResponse = mapper.toApiResponse(appResponse);

            // then
            assertThat(apiResponse.id()).isEqualTo(1L);
            assertThat(apiResponse.totalAmount())
                .isEqualByComparingTo(BigDecimal.valueOf(30000));
        }

        @Test
        @DisplayName("PageResponse → OrderPageApiResponse 변환")
        void givenPageResponse_whenToPageApiResponse_thenSuccess() {
            // given
            var content = List.of(
                new OrderDetailResponse(1L, null, List.of(), BigDecimal.ZERO, "PLACED", null)
            );
            var appPageResponse = new PageResponse<>(content, 0, 20, 100L);

            // when
            OrderPageApiResponse apiResponse = mapper.toPageApiResponse(appPageResponse);

            // then
            assertThat(apiResponse.content()).hasSize(1);
            assertThat(apiResponse.totalElements()).isEqualTo(100L);
        }
    }
}

9) 체크리스트


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