컨트롤러 단위 테스트
4주차 과제는 컨트롤러 단위 테스트였다! 과제의 상세 설명은 아래와 같다.
- 컨트롤러 단위테스트를 작성한뒤 소스코드를 업로드, stub을 구현하시오.
Stub이란?
과제를 구현하기 위해서는 일단 Stub에 대해 이해하고 있었어야 했는데 Stub에 대해 이해하는 것에 시간이 조금 걸렸던 것 같다.
여러 서치와 강의 자료를 통해 내가 이해한 Stub을 간단하게 말하자면 어떤 함수를 실행할 때, 그 결과값을 임의로 설정하는 것이다.
컨트롤러 테스트에서는 보낸 요청에 대해 응답이 적절한가를 검증하기 때문에 결과값이 실제 데이터인지는 중요하지 않다.
Stub 적용
예제를 통해 Stub을 적용해보자. 아래는 장바구니 전체 내역 보기에 대해 진행한 테스트이다.
일단, 테스트를 하기전에 Controller단을 확인하자면, 장바구니 전체 내역 보기 ("/carts", post) 를 요청 시에는 다음의 조회 내역들이 필요하다.
- 장바구니 내역들을 알아야 하고 (getCartList())
- 장바구니 내역들을 알기 위해서는 담은 제품의 옵션 내역(옵션, 수량, 가격 등)을 알아야 하고
- 제품의 옵션 내역을 알기 위해서는 그 제품에 대해 알아야 한다.
CartController
@PostMapping("/carts/update")
public ResponseEntity<?> update(@RequestBody @Valid List<CartRequest.UpdateDTO> requestDTOs, @AuthenticationPrincipal CustomUserDetails userDetails) {
// 가짜 저장소의 값을 변경한다.
for (CartRequest.UpdateDTO updateDTO : requestDTOs) {
for (Cart cart : fakeStore.getCartList()) {
// 카트 업데이트 로직 수행
}
}
// DTO를 만들어서 응답한다.
CartResponse.UpdateDTO responseDTO = new CartResponse.UpdateDTO(fakeStore.getCartList());
return ResponseEntity.ok().body(ApiUtils.success(responseDTO));
}
CartController에서는 fakeStore(서비스단을 대체한 더미 데이터 클래스)를 getCartList()를 호출하게 된다.
1차 Stub 적용 (에러 🤯)
앞에서 말했듯 Stub은 어떤 함수를 실행할 때, 그 결과값을 임의로 설정하는 것이다.
장바구니를 조회한 구현한 Stub
반복하자면, 장바구니 조회가 실행되면 장바구니 목록을 불러오는 메서드가 실행된다.
Mockito.when(fakeStore.getCartList()).thenReturn(
Arrays.asList(
new Cart(1, user, fakeStore.getOptionList().get(0), 5, 50000),
new Cart(2, user, fakeStore.getOptionList().get(1), 5, 54500)
)
);
이때 장바구니 목록을 조회하려면 옵션 목록 또한 조회해야 하는데, 옵션목록을 조회하려면 또 제품을 조회해야 되는 이른바
꼬꼬무 사태가 발생한다.
따라서 1차 구현한 전체 Stub
// stub
Mockito.when(fakeStore.getProductList()).thenReturn(
Arrays.asList(new Product(1, "기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전", "", "/images/1.jpg", 1000)
)
);
Mockito.when(fakeStore.getOptionList()).thenReturn(
Arrays.asList(
new Option(1, fakeStore.getProductList().get(0), "01. 슬라이딩 지퍼백 크리스마스에디션 4종", 10000),
new Option(2, fakeStore.getProductList().get(0),"02. 슬라이딩 지퍼백 플라워에디션 5종", 10900)
)
);
Mockito.when(fakeStore.getCartList()).thenReturn(
Arrays.asList(
new Cart(1, user, fakeStore.getOptionList().get(0), 5, 50000),
new Cart(2, user, fakeStore.getOptionList().get(1), 5, 54500)
)
);
🤯 문제 발생
3. you are stubbing the behaviour of another mock inside before 'thenReturn' instruction is completed
위의 테스트 코드를 실행하니 에러와 함께 다음의 줄이 표시되었다.
즉, thenReturn이 끝나기 전에 내부에서 다른 Stub을 할 수 없다.
어찌보면 당연하지만... 안된다...!
최종 Stub 적용
따라서 stub 시 사용해야 하는 데이터는 할당을 하고, 그에 맞는 Stub을 구현하도록 해야 한다.
// stub
Product product = new Product(1, "기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전", "", "/images/1.jpg", 1000);
Option option1 = new Option(1, product, "01. 슬라이딩 지퍼백 크리스마스에디션 4종", 10000);
Option option2 = new Option(2, product,"02. 슬라이딩 지퍼백 플라워에디션 5종", 10900);
Mockito.when(fakeStore.getCartList()).thenReturn(
Arrays.asList(
new Cart(1, user, option1, 5, 50000),
new Cart(2, user, option2, 5, 54500)
)
);
// 🔥 you are stubbing the behaviour of another mock inside before 'thenReturn' instruction is completed
// Mockito.when(fakeStore.getProductList()).thenReturn(
// Arrays.asList(new Product(1, "기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전", "", "/images/1.jpg", 1000)
// )
// );
//
// Mockito.when(fakeStore.getOptionList()).thenReturn(
// Arrays.asList(
// new Option(1, fakeStore.getProductList().get(0), "01. 슬라이딩 지퍼백 크리스마스에디션 4종", 10000),
// new Option(2, fakeStore.getProductList().get(0),"02. 슬라이딩 지퍼백 플라워에디션 5종", 10900)
// )
// );
정상적으로 테스트가 통과되었다.
최종 컨트롤러 단위 테스트 코드
// (기능 9) 장바구니 보기
@WithMockUser(username = "ssar@nate.com", roles = "USER")
@Test
public void findAll_test() throws Exception {
// given
User user = User.builder()
.id(1)
.roles("ROLE_USER")
.build();
// stub
// 🔥 you are stubbing the behaviour of another mock inside before 'thenReturn' instruction is completed
// Mockito.when(fakeStore.getProductList()).thenReturn(
// Arrays.asList(new Product(1, "기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전", "", "/images/1.jpg", 1000)
// )
// );
//
// Mockito.when(fakeStore.getOptionList()).thenReturn(
// Arrays.asList(
// new Option(1, fakeStore.getProductList().get(0), "01. 슬라이딩 지퍼백 크리스마스에디션 4종", 10000),
// new Option(2, fakeStore.getProductList().get(0),"02. 슬라이딩 지퍼백 플라워에디션 5종", 10900)
// )
// );
Product product = new Product(1, "기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전", "", "/images/1.jpg", 1000);
Option option1 = new Option(1, product, "01. 슬라이딩 지퍼백 크리스마스에디션 4종", 10000);
Option option2 = new Option(2, product,"02. 슬라이딩 지퍼백 플라워에디션 5종", 10900);
Mockito.when(fakeStore.getCartList()).thenReturn(
Arrays.asList(
new Cart(1, user, option1, 5, 50000),
new Cart(2, user, option2, 5, 54500)
)
);
// when
ResultActions result = mvc.perform(
MockMvcRequestBuilders
.get("/carts")
.contentType(MediaType.APPLICATION_JSON)
);
String responseBody = result.andReturn().getResponse().getContentAsString();
System.out.println("테스트 : " + responseBody);
// then
result.andExpect(MockMvcResultMatchers.jsonPath("$.success").value("true"));
result.andExpect(MockMvcResultMatchers.jsonPath("$.response.products[0].id").value("1"));
result.andExpect(MockMvcResultMatchers.jsonPath("$.response.products[0].productName").value("기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전"));
// ...
}
'카카오테크캠퍼스 > 2단계' 카테고리의 다른 글
[카카오테크캠퍼스] 2단계 회고 (0) | 2023.08.11 |
---|---|
[카테캠] 2주차 정리 (0) | 2023.07.11 |
[1주차 과제] 추가 기능 도출 및 API요청 분석 (0) | 2023.07.01 |
[카테캠 2단계] API 요청해보기 (0) | 2023.06.26 |
[카테캠 2단계] 1일차 2단계 클론코딩 과정 보기 (0) | 2023.06.26 |