예약 시스템 API - 댓글 목록 조회

Seyeong·2023년 4월 15일
0

이번 글에서는 댓글 목록을 조회하는 API를 만들어 보겠습니다.

요청 및 응답은 아래와 같습니다.

API 문서

요청

응답

이제 댓글 목록을 가져오는 기능을 구현해봅시다.

Repository 테스트 코드 작성 - 1

JdbcCommentRepositoryTest

@DataJdbcTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
public class JdbcCommentRepositoryTest {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    private CommentRepository repository;

    @BeforeEach
    void setUp() {
        repository = new JdbcCommentRepository(jdbcTemplate);
    }

    @Test
    void testGetReservationComments() {
        // when
        List<CommentResponseDto> actual = repository.getReservationComments(1);

        // then
        assertThat(actual.size()).isEqualTo(15);
    }

    public static CommentsResponseDto getComments() {
        return CommentsResponseDto.builder()
                .totalCount(15)
                .commentCount(5)
                .reservationUserComments(List.of(
                        CommentResponseDto.builder().id(11).build(),
                        CommentResponseDto.builder().id(12).build(),
                        CommentResponseDto.builder().id(13).build(),
                        CommentResponseDto.builder().id(14).build(),
                        CommentResponseDto.builder().id(15).build()
                ))
                .build();
    }
}

하나의 댓글을 조회하기 위한 여러 개의 테스트 코드를 작성해주었습니다.

원래 테스트 코드 하나씩 보여주면서 순서대로 작성해나가는걸 보여주려고 했지만, 이전까지 그렇게 했기 때문에 속도를 높이기 위해서 한번에 작성해주었습니다.

Repository 구현 코드 작성

순서대로 테스트 코드를 통과시켜 보도록 하겠습니다.
먼저 testGetReservationComments() 테스트 코드부터 통과시켜 보겠습니다.

CommentRepository

public interface CommentRepository {
    List<CommentResponseDto> getReservationComments(int productId);
}

JdbcCommentRepository

@Repository
@RequiredArgsConstructor
public class JdbcCommentRepository implements CommentRepository {

	...

    @Override
    public List<CommentResponseDto> getReservationComments(int productId) {
        return jdbcTemplate.query(
                CommentSQLMapper.SELECT_RESERVATION_COMMENTS,
                CommentResponseDto.commentMapper,
                productId
        );
    }
}

CommentSQLMapper

public class CommentSQLMapper {
	
    ...

    public static final String SELECT_RESERVATION_COMMENTS =
            "SELECT ruc.id id, ruc.product_id productId, ruc.reservation_info_id reservationInfoId, ruc.score score, u.email reservationEmail, ruc.comment comment, ruc.create_date createDate, ruc.modify_date modifyDate\n"
            + "FROM reservation_user_comment ruc, user u\n"
            + "WHERE ruc.user_id = u.id AND ruc.product_id = ?";
}

CommentResponseDto

@Builder
@Getter
@RequiredArgsConstructor
@NoArgsConstructor(force = true)
public class CommentResponseDto /*implements RestDocsTemplate*/ {
    private final int id;
    private final int productId;
    private final int reservationInfoId;
    private final int score;
    private final String reservationEmail;
    private final String comment;
    private final LocalDateTime createDate;
    private final LocalDateTime modifyDate;
    private final List<CommentImage> reservationUserCommentImages;

    public static final RowMapper<CommentResponseDto> commentMapper = (rs, rowNum) -> {
        return CommentResponseDto.builder()
                .id(rs.getInt("id"))
                .productId(rs.getInt("productId"))
                .reservationInfoId(rs.getInt("reservationInfoId"))
                .score(rs.getInt("score"))
                .reservationEmail(rs.getString("reservationEmail"))
                .comment(rs.getString("comment"))
                .createDate(rs.getObject("createDate", LocalDateTime.class))
                .modifyDate(rs.getObject("modifyDate", LocalDateTime.class))
                .build();
    };
	
    ... // REST-Docs 필드 명세

}

이렇게하면 testGetReservationComments() 테스트 코드가 통과할 겁니다. 직관적으로 보이기 위해 RestDocsTemplate 인터페이스를 구현하고 REST-Docs 필드 명세에 대한 부분은 주석처리 해주었습니다.

이제 실제로 댓글들에 대한 응답 테스트인 getComments() 를 통과시켜 보겠습니다.

Repository 테스트 코드 작성 - 2

JdbcCommentRepositoryTest

...
public class JdbcCommentRepositoryTest {
    
    ...

    @ParameterizedTest
    @CsvSource(value = {"11,11,5,5", "15,15,1,1"})
    void testGetComments(int start, int expectedCommentId, int expectedSize) {
        // given
        CommentsRequestDto requestDto = CommentsRequestDto.builder()
                .productId(1)
                .start(start)
                .build();

        // when
        CommentsResponseDto actual = repository.getComments(requestDto);

        // then
        assertThat(actual.getTotalCount()).isEqualTo(15);
        assertThat(actual.getReservationUserComments().get(0).getId()).isEqualTo(expectedCommentId);
        assertThat(actual.getCommentCount()).isEqualTo(expectedSize);
        assertThat(actual.getReservationUserComments().size()).isEqualTo(expectedSize);
    }
}

위의 테스트에서 파라미터를 주고 테스트를 해주었습니다. 그 파라미터로는 댓글들을 조회할 시작 인덱스(start)와, 그로인해 조회되는 첫번째 댓글의 아이디(expectedCommentId), 그리고 조회된 댓글의 수(expectedSize)를 의미합니다.

이렇게 테스트를 해주는 이유는, 만약 댓글이 총 15개일 때 댓글을 조회하면 현재는 최대 5개의 댓글을 한번에 볼 수 있게 되어 있는데, 문제는 이게 가능하려면 최대 조회할 인덱스 시작 위치가 11이어야 합니다.
그래야 11 ~ 15까지 총 5개의 댓글을 조회할 것이기 때문이죠.

그런데 만약 조회를 시작할 인덱스가 12, 13.. 이렇게 되어버리면, 기대되는 조회의 수(expectedSize)가 그만큼 줄어들 것이기 때문에 그 부분에 대한 로직도 테스트해주는 것입니다.

Repository 구현 코드 작성

JdbcCommentRepository

@Repository
@RequiredArgsConstructor
public class JdbcCommentRepository implements CommentRepository {
    private static final int SHOW_COMMENTS_COUNT = 5;
    
    ...

    @Override
    public CommentsResponseDto getComments(CommentsRequestDto requestDto) {
        List<CommentResponseDto> reservationComments = getReservationComments(requestDto.getProductId());
        List<CommentResponseDto> commentCountResults = getCommentCountResult(reservationComments, requestDto);
        
        return CommentsResponseDto.builder()
                .totalCount(reservationComments.size())
                .commentCount(commentCountResults.size())
                .reservationUserComments(commentCountResults)
                .build();
    }

    private List<CommentResponseDto> getCommentCountResult(List<CommentResponseDto> results,
                                                           CommentsRequestDto requestDto) {
        int start = Math.min(results.size(), requestDto.getStart()) - 1;
        int end = Math.min(results.size(), start + SHOW_COMMENTS_COUNT);

        return IntStream.range(start, end)
                .mapToObj(results::get)
                .collect(Collectors.toList());
    }
    
    ... // getReservationComments() 
    
}

아래의 getCommentCountResult( ) 메서드는 총 댓글 수와 조회를 시작할 인덱스를 토대로 조회할 댓글의 start ~ end 범위를 정해서 댓글들을 반환합니다.

이렇게 댓글들을 조회하는 Repository에 대한 구현이 끝났습니다.

Service 테스트 코드 작성

CommentServiceTest

class CommentServiceTest {
    @InjectMocks
    private CommentService service;

    @Mock CommentRepository repository;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    void testGetComments() {
        // given
        CommentsResponseDto expected = JdbcCommentRepositoryTest.getComments();
        CommentsRequestDto requestDto = CommentsRequestDto.builder().productId(1).start(11).build();

        // when
        when(repository.getComments(any(CommentsRequestDto.class))).thenReturn(expected);
        CommentsResponseDto actual = service.getComments(requestDto);

        // then
        assertThat(actual).isEqualTo(expected);
    }
}

Service 구현 코드 작성

CommentService

@Service
@RequiredArgsConstructor
public class CommentService {
    private final CommentRepository repository;

    public CommentsResponseDto getComments(CommentsRequestDto requestDto) {
        return repository.getComments(requestDto);
    }
}

Service 테스트 코드에서 Repository를 모킹했기 때문에 Service에서는 단순히 Repository 메서드를 호출해주기만 하면 됩니다.

물론, 이렇게 테스트를 통과하는데에 있어서 Repsoitory의 getComments() 가 정상적인 결과를 반환한다는 확신이 있어야 합니다.

저희는 Repository에 대한 테스트를 먼저 통과시켰기 때문에 Repository#getComments() 가 정상적으로 동작한다는 확신이 있으므로, 위와 같은 코드로도 작성이 가능합니다.

이제 컨트롤러 코드를 작성해봅시다.

Controller 테스트 코드 작성

CommentControllerTest

class CommentControllerTest {
    private ObjectMapper objectMapper = new ObjectMapper();
    private MockMvc mockMvc;

    @InjectMocks
    private CommentController controller;

    @Mock
    private CommentService service;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
    }

    @Test
    void testGetComments() throws Exception {
        // given
        CommentsResponseDto expected = JdbcCommentRepositoryTest.getComments();
        CommentsRequestDto requestDto = CommentsRequestDto.builder().productId(1).start(11).build();
        String URI = "/api/comments";

        // when
        when(service.getComments(any(CommentsRequestDto.class))).thenReturn(expected);

        // then
        mockMvc.perform(get(URI)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(requestDto)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.totalCount").value(expected.getTotalCount()))
                .andExpect(jsonPath("$.commentCount").value(expected.getCommentCount()))
                .andExpect(jsonPath("$.reservationUserComments").isArray());
    }
}

"/api/comments" 경로로 요청이 오면 그에 대한 응답이 제대로 이루어지는지에 대한 테스트입니다. 테스트를 통과하기 위해선 아래와 같이 구현해주면 됩니다.

Controller 구현 코드 작성

CommentController

@RequiredArgsConstructor
@RestController
@RequestMapping("/api")
public class CommentController {
    private final CommentService service;

    @GetMapping("/comments")
    public CommentsResponseDto getComments(@RequestBody CommentsRequestDto requestDto) {
        return service.getComments(requestDto);
    }
}

이렇게 작성해주면 테스트는 통과하게 됩니다.

이번 장에서는 상품에 대한 댓글을 조회하는 API를 작성해보았습니다.

다음은 지금껏 작성했던 API들에 대한 예외처리를 해주도록 하겠습니다.

0개의 댓글