본문 바로가기
JAVA/SpringBoot

[SpringBoot] JUnit 테스트코드 - Controller

by 설총이 2023. 7. 31.

- 소스 확인

@WebMvcTest(ProductController.class)
//@AutoConfigureWebMvc // 이 어노테이션을 통해 MockMvc를 Builder 없이 주입받을 수 있음
public class ProductControllerTest {

    @Autowired
    private MockMvc mockMvc;

    // ProductController에서 잡고 있는 Bean 객체에 대해 Mock 형태의 객체를 생성해줌
    @MockBean
    ProductServiceImpl productService;

    // http://localhost:8080/api/v1/product-api/product/{productId}
    @Test
    @DisplayName("Product 데이터 가져오기 테스트")
    void getProductTest() throws Exception {

        // given : Mock 객체가 특정 상황에서 해야하는 행위를 정의하는 메소드
        given(productService.getProduct("12315")).willReturn(
                new ProductDto("15871", "pen", 5000, 2000));

        String productId = "12315";

        // andExpect : 기대하는 값이 나왔는지 체크해볼 수 있는 메소드
        mockMvc.perform(
                        get("/api/v1/product-api/product/" + productId))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.productId").exists()) // json path의 depth가 깊어지면 .을 추가하여 탐색할 수 있음 (ex : $.productId.productIdName)
                .andExpect(jsonPath("$.productName").exists())
                .andExpect(jsonPath("$.productPrice").exists())
                .andExpect(jsonPath("$.productStock").exists())
                .andDo(print());

        // verify : 해당 객체의 메소드가 실행되었는지 체크해줌
        verify(productService).getProduct("12315");
    }

    @Test
    @DisplayName("Product 데이터 생성 테스트")
    void createProductTest() throws Exception {
        //Mock 객체에서 특정 메소드가 실행되는 경우 실제 Return을 줄 수 없기 떄문에 아래와 같이 가정 사항을 만들어줌
        given(productService.saveProduct("15871", "pen", 5000, 2000))
                .willReturn(new ProductDto("15871", "pen", 5000, 2000));

        ProductDto productDto = ProductDto.builder().productId("15871").productName("pen").productPrice(5000).productStock(2000).build();

        /*Gson gson = new Gson();
        String content = gson.toJson(productDto);*/

        // Gson대신 dto 객체를 json형태 변경 작업을 대체 가능
        String content = new ObjectMapper().writeValueAsString(productDto);

        mockMvc.perform(
          post("/api/v1/product-api/product")
                  .content(content)
                  .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isAccepted())
                .andExpect(jsonPath("$.productId").exists())
                .andExpect(jsonPath("$.productName").exists())
                .andExpect(jsonPath("$.productPrice").exists())
                .andExpect(jsonPath("$.productStock").exists())
                .andDo(print());

        verify(productService).saveProduct("15871", "pen", 5000, 2000);
    }
}

 

1) getProductTest 확인

// http://localhost:8080/api/v1/product-api/product/{productId}
    @Test
    @DisplayName("Product 데이터 가져오기 테스트")
    void getProductTest() throws Exception {

        // given : Mock 객체가 특정 상황에서 해야하는 행위를 정의하는 메소드
        given(productService.getProduct("12315")).willReturn(
                new ProductDto("15871", "pen", 5000, 2000));

        String productId = "12315";

        // andExpect : 기대하는 값이 나왔는지 체크해볼 수 있는 메소드
        mockMvc.perform(
                        get("/api/v1/product-api/product/" + productId))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.productId").exists()) // json path의 depth가 깊어지면 .을 추가하여 탐색할 수 있음 (ex : $.productId.productIdName)
                .andExpect(jsonPath("$.productName").exists())
                .andExpect(jsonPath("$.productPrice").exists())
                .andExpect(jsonPath("$.productStock").exists())
                .andDo(print());

        // verify : 해당 객체의 메소드가 실행되었는지 체크해줌
        verify(productService).getProduct("12315");
    }

[2023-07-31 17:32:38.976] [INFO ] [main] c.s.s.i.c.ProductController [ProductController] perform '1690792358976' of seypak API.
[2023-07-31 17:32:38.980] [INFO ] [main] c.s.s.i.c.ProductController [ProductController] Response :: productId ='15871', productName ='pen', productPrice ='5000', productStock ='2000', Response Time = '4'ms

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /api/v1/product-api/product/12315
       Parameters = {}
          Headers = []
             Body = null
    Session Attrs = {}

Handler:
             Type = com.seypak.study.intellij.controller.ProductController
           Method = co m.seypak.study.intellij.controller.ProductController#getProduct(String) 

비동기로 처리했는지

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

뷰를 다루고있지 않기때문에 null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = {"productId":"15871","productName":"pen","productPrice":5000,"productStock":2000}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

 

2) createProductTest 확인

@Test
    @DisplayName("Product 데이터 생성 테스트")
    void createProductTest() throws Exception {
        //Mock 객체에서 특정 메소드가 실행되는 경우 실제 Return을 줄 수 없기 떄문에 아래와 같이 가정 사항을 만들어줌
        given(productService.saveProduct("15871", "pen", 5000, 2000))
                .willReturn(new ProductDto("15871", "pen", 5000, 2000));

        ProductDto productDto = ProductDto.builder().productId("15871").productName("pen").productPrice(5000).productStock(2000).build();

        /*Gson gson = new Gson();
        String content = gson.toJson(productDto);*/

        // Gson대신 dto 객체를 json형태 변경 작업을 대체 가능
        String content = new ObjectMapper().writeValueAsString(productDto);

        mockMvc.perform(
          post("/api/v1/product-api/product")
                  .content(content)
                  .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isAccepted())
                .andExpect(jsonPath("$.productId").exists())
                .andExpect(jsonPath("$.productName").exists())
                .andExpect(jsonPath("$.productPrice").exists())
                .andExpect(jsonPath("$.productStock").exists())
                .andDo(print());

        verify(productService).saveProduct("15871", "pen", 5000, 2000);
    }

 

[2023-07-31 18:07:23.069] [INFO ] [main] c.s.s.i.c.ProductController [createProduct] Response >> productId ='15871', productName ='pen', productPrice ='5000', productStock ='2000'

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /api/v1/product-api/product
       Parameters = {}
          Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"81"]
             Body = {"productId":"15871","productName":"pen","productPrice":5000,"productStock":2000}
    Session Attrs = {}

Handler:
             Type = com.seypak.study.intellij.controller.ProductController
           Method = co m.seypak.study.intellij.controller.ProductController#createProduct(ProductDto) 

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 202
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = {"productId":"15871","productName":"pen","productPrice":5000,"productStock":2000}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

Process finished with exit code 0

 

예외발생테스트1) .productId를 1111로 변경했는데 Null Exception이 발생해 원인파악중.

ProductDto productDto = ProductDto.builder().productId("1111").productName("pen").productPrice(5000).productStock(2000).build();

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
at co m.seypak.study.intellij.controller.ProductControllerTest.createProductTest(ProductControllerTest.java:73) 

Caused by: java.lang.NullPointerException
at co m.seypak.study.intellij.controller.ProductController.createProduct(ProductController.java:61) 
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1063)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
... 85 more

Class transformation time: 0.0878916s for 7125 classes or 1.2335663157894737E-5s per class

Process finished with exit code -1