๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ“• Spring Framework/Spring Project

ใ€Œ์ปจํŠธ๋กค๋Ÿฌ ๋‹จ์œ„ ํ…Œ์ŠคํŠธใ€

by GroovyArea 2022. 6. 23.

https://github.com/GroovyArea/MyChickenBreastShop

 

GitHub - GroovyArea/MyChickenBreastShop: ChikenBreastShop API with Spring boot

ChikenBreastShop API with Spring boot. Contribute to GroovyArea/MyChickenBreastShop development by creating an account on GitHub.

github.com

์ €๋ฒˆ ์ฃผ์— ์ฒ˜์Œ์œผ๋กœ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ๊ณต๋ถ€ํ•˜๊ณ  ์ ์šฉ์‹œํ‚ค๋ฉฐ Spring REST Docs๋ฅผ ์ด์šฉํ•ด ์ ์šฉ์‹œ์ผฐ๋‹ค. 
์ฒ˜์Œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋‹ค ๋ณด๋‹ˆ ๋ฏธํกํ•œ ๋ถ€๋ถ„์ด ์ข€ ์žˆ์—ˆ๋‹ค. ๊ทธ ๋ถ€๋ถ„์„ ์ˆ˜์ •ํ•ด๊ฐ€๋ฉฐ ๋‹ค๋ฅธ ์ปจํŠธ๋กค๋Ÿฌ์˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€๋กœ ์ž‘์„ฑํ–ˆ๋‹ค.
API ๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ๋„ ํ†ตํ•ฉํ•ด์„œ ์ž‘์„ฑ ์ค‘์ด์—ˆ๋Š”๋ฐ, ๋„๋ฉ”์ธ ๋ณ„๋กœ ๋‚˜๋ˆ„์–ด ์ˆ˜์ •ํ•˜๋ฉฐ ์ž‘์„ฑํ•ด๋ณด์•˜๋‹ค.
์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋งˆ์น˜๊ฒŒ ๋˜๋ฉด ์„œ๋น„์Šค ๊ณ„์ธต์˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋„ ์˜๋ฏธ๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ ์ž‘์„ฑํ•ด ๋ณผ ์ƒ๊ฐ์ด๋‹ค.

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ์ด ์™„๋ฃŒ ๋˜๋ฉด ์„ฑ๋Šฅ ์ธก์ •์„ ํ•˜๋ฉฐ, ๋ฆฌํŒฉํ„ฐ๋ง์„ ํ•  ๊ณ„ํš๋„ ์ˆ˜๋ฆฝํ–ˆ๋‹ค.

ํ† ๋น„์˜ ์Šคํ”„๋ง ์ฑ…์€ ๊ฑฐ์˜ ๋‹ค ์ฝ์–ด ๊ฐ€๋Š”๋ฐ, ์ •๋ง ์ข‹์€ ์ฑ… ๊ฐ™๋‹ค. ์Šคํ”„๋ง์„ ์ œ๋Œ€๋กœ ๊ณต๋ถ€ํ•˜๋Š” ์‚ฌ๋žŒ์—๊ฒŒ ๋ฌด์กฐ๊ฑด ์ถ”์ฒœํ•˜๊ณ  ์‹ถ์€ ์„œ์ ์ด๋‹ค. ์–ด๋ ต์ง€๋งŒ ์ •ํ™•ํ•˜๊ฒŒ ์ดํ•ดํ•˜๋ฉฐ ๋งˆ๋ฌด๋ฆฌํ•˜๊ณ  ์–ผ๋ฅธ JPA๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉฐ ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์— ์ ์šฉ์‹œ์ผœ๋ณด๊ณ  ์‹ถ๋‹ค. 

 

์žฅ๋ฐ”๊ตฌ๋‹ˆ ์ปจํŠธ๋กค๋Ÿฌ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ

์ €๋ฒˆ ์ฃผ์— ํšŒ์›, ์ƒํ’ˆ ์ปจํŠธ๋กค๋Ÿฌ์˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ–ˆ๋‹ค. ์ด๋ฒˆ 3์ผ๊ฐ„ ์ง„ํ–‰ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ๊ธฐ์กด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์˜ ๋ฌธ์ œ์ ์„ ๋ณด์™„ํ•ด ์ง„ํ–‰ํ–ˆ๋‹ค.

 

@BeforeEach
void setUpCookie() {
    cartItemDTOMap.put(addCartDTO1.getProductNo(), addCartDTO1);
    cartItemDTOMap.put(addCartDTO2.getProductNo(), addCartDTO2);

    cookieList.add(cookie1);
    cookieList.add(cookie2);
    cookieList.add(cookie3);
    cookieList.add(cookie4);

    newCartCookie = new Cookie("Chicken", URLEncoder.encode(JsonUtil.objectToString(cartItemDTOMap), StandardCharsets.UTF_8));
    newCartCookie.setMaxAge(60 * 60 * 24 * 7);
    newCartCookie.setPath("/api");

    cookieList.add(newCartCookie);
    cookies = cookieList.toArray(Cookie[]::new);
}

ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ๊ธฐ๋ณธ์ ์œผ๋กœ ์ฟ ํ‚ค ๊ฐ’์„ ์„ธํŒ…ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— @BeforeEach๋ฅผ ์‚ฌ์šฉํ•ด Setup ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

 

@Test
@DisplayName("์žฅ๋ฐ”๊ตฌ๋‹ˆ ์กฐํšŒ ํ…Œ์ŠคํŠธ")
void getCartListTest() throws Exception {
    Mockito.when(cartService.getCartCookie(cookies)).thenReturn(newCartCookie);
    Mockito.when(cartService.getCartDTOMap(newCartCookie)).thenReturn(cartItemDTOMap);

    mockMvc.perform(get("/api/carts")
                    .cookie(cookies))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$[0]").exists())
            .andExpect(jsonPath("$[1]").exists())
            .andDo(print());
}

๊ธฐ๋ณธ์ ์ธ ์žฅ๋ฐ”๊ตฌ๋‹ˆ ์กฐํšŒ ํ…Œ์ŠคํŠธ์ด๋‹ค. @Mockbean ๋œ ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ์˜ ๋™์ž‘์„ ๋จผ์ € ์ •์˜ํ•˜๊ณ (given) ๊ฒฐ๊ณผ ๊ฐ’์˜ ์กด์žฌ ์œ ๋ฌด๋กœ ํŒ๋‹จํ–ˆ๋‹ค(when & then).

 

@Test
@DisplayName("์žฅ๋ฐ”๊ตฌ๋‹ˆ ์ƒํ’ˆ ์‚ญ์ œ ํ…Œ์ŠคํŠธ")
void deleteCart() throws Exception {
    cartItemDTOMap.remove(deleteCartDTO.getProductNo());

    cookieList.add(newCartCookie);

    Mockito.when(cartService.getCartCookie(cookies)).thenReturn(newCartCookie);
    Mockito.when(cartService.getCartDTOMap(newCartCookie)).thenReturn(cartItemDTOMap);
    Mockito.when(cartService.resetCartCookie(newCartCookie, cartItemDTOMap)).thenReturn(newCartCookie);

    mockMvc.perform(delete("/api/carts")
                    .cookie(cookies)
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(JsonUtil.objectToString(deleteCartDTO)))
            .andExpect(status().isOk())
            .andExpect(content().string(ResponseMessage.DELETE_MESSAGE.getValue()))
            .andDo(print());
}

๋‚˜๋จธ์ง€ ํ…Œ์ŠคํŠธ๋Š” ๊ฑฐ์˜ ๋น„์Šทํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‚ญ์ œ ํ…Œ์ŠคํŠธ๋„ ๊ฑฐ์˜ ๋น„์Šทํ•œ ๊ตฌํ˜„์ด๋‹ค. 

 

ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ ํ›„ ์ƒ์„ฑ๋œ snippets ํŒŒ์ผ๋“ค๋กœ ๋ช…์„ธ๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค. ๋ณธ ๊ทธ๋ฆผ์€ ๋„๋ฉ”์ธ ๋ณ„๋กœ ๋‚˜๋ˆˆ ๋ชจ์Šต์ด๋‹ค.

 

์ฃผ๋ฌธ ์ปจํŠธ๋กค๋Ÿฌ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ

์ฃผ๋ฌธ ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์นด์นด์˜คํŽ˜์ด REST API๋ฅผ ์ด์šฉํ•ด ๊ตฌํ˜„ํ–ˆ์—ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋„˜๊ฒจ๋ฐ›๊ณ  ์ค„ ํŒŒ๋ผ๋ฏธํ„ฐ๋“ค์ด ์—„์ฒญ ๋งŽ๋‹ค. 

๊ทธ๋ž˜์„œ ์•  ์ข€ ๋จน์—ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  mockito์˜ ๊ฐœ๋… ์ •๋ฆฝ์— ํ™•์‹คํžˆ ๋” ๋‹ค์ ธ๊ฐ„ ํ…Œ์ŠคํŠธ์ด๊ธฐ๋„ ํ•˜๋‹ค.

 

@Test
@DisplayName("ํšŒ์› ์ฃผ๋ฌธ ์กฐํšŒ ๋ฆฌ์ŠคํŠธ ํ…Œ์ŠคํŠธ")
void getDBOrderInfo() throws Exception {
    List<OrderInfoDTO> orderInfoDTOList = new ArrayList<>();
    orderInfoDTOList.add(orderInfoDTO);
    Mockito.when(orderService.getOrderInfoList(orderInfoDTO.getPartnerUserId())).thenReturn(orderInfoDTOList);

    mockMvc.perform(get("/api/order/{userId}", orderInfoDTO.getPartnerUserId())
                    .header("Authorization", "Bearer ${ADMIN_AUTH_TOKEN}"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.data").exists())
            .andExpect(jsonPath("$.message", is("ํšŒ์› ์ฃผ๋ฌธ ์กฐํšŒ ๋ฆฌ์ŠคํŠธ์ž…๋‹ˆ๋‹ค.")))
            .andDo(print());
}

DB์— ์ €์žฅ๋œ ์ฃผ๋ฌธ ์™„๋ฃŒ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋Š” ์•„์ฃผ ๊ธฐ๋ณธ์ ์ธ ํ…Œ์ŠคํŠธ์ด๋‹ค.

 

@Test
@DisplayName("๋‹จ๊ฑด ๊ฒฐ์ œ ํ…Œ์ŠคํŠธ")
void orderAction() throws Exception {

    String userId = orderInfoDTO.getPartnerUserId();
    String url = "https://online-pay.kakao.com/mockup/v1/cd749f82c8c58decb5c832ab45a0990e02c87b07e32bf3f28aa9b9297a0cf710/info";

    Mockito.when(kakaoPayService.getkakaoPayUrl(any(OrderProductDTO.class), anyString(), anyString())).thenReturn(url);

    mockMvc.perform(post("/api/order")
                    .header("Authorization", "Bearer ${ADMIN_AUTH_TOKEN}")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(JsonUtil.objectToString(orderProductDTO))
                    .requestAttr("tokenUserId", userId))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.data", is(url)))
            .andExpect(jsonPath("$.message", is("์นด์นด์˜ค ํŽ˜์ด ๊ฒฐ์ œ URL")))
            .andDo(print());
}

 

@Test
@DisplayName("์žฅ๋ฐ”๊ตฌ๋‹ˆ ๊ฒฐ์ œ ํ…Œ์ŠคํŠธ")
void cartOrderAction() throws Exception {
    given(cartService.getCartCookie(cookies)).willReturn(newCartCookie);

    String userId = orderInfoDTO.getPartnerUserId();
    String url = "https://online-pay.kakao.com/mockup/v1/ce3cb801e74a337fb7cf74d9a9aa139bb569e74ba316ae7eb282b90f8f08a522/info";

    given(kakaoPayService.getCartKakaoPayUrl(any(Integer[].class), any(String[].class),
            any(Integer[].class), anyInt(), anyString(), anyString())).willReturn(url);

    mockMvc.perform(post("/api/order/cart")
                    .header("Authorization", "Bearer ${ADMIN_AUTH_TOKEN}")
                    .cookie(cookies)
                    .requestAttr("tokenUserId", userId))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.data", is(url)))
            .andExpect(jsonPath("$.message", is("์นด์นด์˜ค ํŽ˜์ด ๊ฒฐ์ œ URL")))
            .andDo(print());
}

์—ฌ๊ธฐ์„œ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค. Mockbean ์ •์˜ํ•œ ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์—ฌ๋Ÿฌ๊ฐœ ๋„˜๊ฒจ์ฃผ๋Š” ์ƒํ™ฉ์„ given์œผ๋กœ ์ฃผ์–ด์•ผ ํ•˜๋Š”๋ฐ, ๊ตฌ์ฒด์ ์ธ ๋ณ€์ˆ˜๋ฅผ ์ •์˜ํ•˜์—ฌ ๋„ฃ์œผ๋‹ˆ ๊ณ„์† ๋ฉ”์„œ๋“œ๊ฐ€ null์„ return ํ–ˆ๋‹ค. 

 

์–ด๋””๊ฐ€ ์ž˜๋ชป๋˜์—ˆ๋‚˜ ๊ณ„์† ๋ณด์•˜๊ณ , ๊ฒ€์ƒ‰์„ ํ•œ์ฐธ ํ•ด๋ณด๋‹ˆ, mockito๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ mockbean ์ธ์Šคํ„ด์Šค๋Š” ํ˜ธ์ถœ๋œ json์„ ์—ญ์ง๋ ฌ ํ™”ํ•˜์—ฌ ์ƒˆ๋กญ๊ฒŒ ๋นŒ๋“œ๋œ ์ธ์Šคํ„ด์Šค์ด๋ฏ€๋กœ ๊ธฐ์กด ์ธ์Šคํ„ด์Šค์˜ ์ง€์‹œ์— ์‘๋‹ตํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ

any() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ •์ƒ์ ์œผ๋กœ ๊ฐ’์„ ์–ป๋Š” ๊ฒฐ๊ณผ๋ฅผ ๋‚ด๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

 

์ฐธ์กฐ : https://stackoverflow.com/questions/55498142/mocked-method-return-value-is-null-in-spring-boot-webmvctest

 

Mocked method return value is null in Spring Boot @WebMvcTest

The below test fails with: org.json.JSONException: Unparsable JSON string. I tried various variations of the mocking the saveAndFlush method // doAnswer(returnsFirstArg()).when(this.

stackoverflow.com

 

์ถ”ํ›„ ์ง„ํ–‰ํ•  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ๊ฒฐ์ œ ์ปจํŠธ๋กค๋Ÿฌ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๊ฐ€ ๋‚จ์•˜๋‹ค. ๋ถ€์ง€๋Ÿฐํžˆ ์ž‘์„ฑํ•ด๋ณด์ž~~

 

๊ธˆ์ฃผ ๊ณ„ํš

  1. ํ† ๋น„์˜ ์Šคํ”„๋ง ์™„๋… (์˜ค๋Š˜ ์™„๋…ํ•ด๋ณด์ž)
  2. JPA ๊ณต๋ถ€ ์‹œ์ž‘ (์˜ค๋Š˜ ์‚ด๊นŒ..? ๋‚ด์ผ ์‚ฌ์ž)
  3. ์„œ๋น„์Šค ๊ณ„์ธต ๋‹จ์œ„ ํ…Œ์ŠคํŠธ & ์„ฑ๋Šฅ ์ธก์ • (APM ํ…Œ์ŠคํŠธ)
  4. ์Šค์ผ€์ฅด๋Ÿฌ ์‹คํ—˜ (์˜ˆ์™ธ ๋กค๋ฐฑ ์ฒ˜๋ฆฌ ํ…Œ์ŠคํŠธ)
  5. ์ž๋ฃŒ๊ตฌ์กฐ ์ •๋ฆฌ (๋‚ด์ผ) ๋ฐ ํ•ด๋‹น ๋ฌธ์ œ 3๊ฐœ ํ’€๊ธฐ
  6. http, ์šด์˜์ฒด์ œ ์ž๊ธฐ ์ „ ํ‹ˆํ‹ˆ์ด ์ฝ๊ธฐ
๋ฐ˜์‘ํ˜•