Spring Controller 슬라이스테스트 코드 작성

선종우·2023년 5월 12일
0
post-custom-banner

1. 공부배경

  • Spring MVC Service 테스트 코드 작성에 이어지는 글입니다.

2. 공부 내용

a. @WebMvcTest

  • 설명 : Spring MVC Component(@Controller, @ControllerAdice 등, @Service, @Component, @Repository 빈은 제외된다.) 이 애노테이션을 사용하게 되면 Spring Security와 MockMvc가 자동으로 설정된다. Controller빈과 연계된 클래스들을 사용하기 위해서는 @Import와 @MockBean을 사용해주어야 한다.

  • 관련 공식 문서 : https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTest.html

  • 사용 예시
    1 . 먼저 테스트하고자 하는 컨트롤러와 서비스, MockMvc를 등록해준다.

    @WebMvcTest(ArticleController.class)
    class ArticleControllerTest{
    	//테스트를 수행해 줄 MockMvc를 선언한다.
       @Autowired MockMvc mvc;
       //컨트롤러에 대한 단위테스트이므로 서비스 코드의 영향을 없애기 위해 가짜 객체를 만들어준다.
       /*MockBean 대상 객체는 스프링 컨테이너에 의해 관리된다.
         따라서 @SpringBootTest와 같이 스프링 컨테이너를 이용하는 경우에는 @MockBean을 사용하고 그렇지 않은 경우에는 @Bean을 사용하면 된다. */
       @MockBean ArticleService articleService;
    }

    2 . 테스트 코드 메소드를 작성해 준다. 기본적으로 perfor()메소드 내에 애플리케이션에 대해 하고자 하는 행위(GET, POST 등을 이용한 값 전달)를 작성하고, andExpect()를 이용해 기대하고자 하는 값이 정상적으로 나오는지 검증한다. 마지막으로는 .andDo()를 이용해 결과값 출력 등을 해볼 수도 있다.

    @WebMvcTest(ArticleController.class)
    class ArticleControllerTest{
    	
       @Autowired MockMvc mvc;
       @MockBean ArticleService articleService;
       
       @Test
       @DisplayName("[GET]특정 게시글 조회 테스트(게시글 O)")
       public void getArticleTest() throws Exception {
           Long articleId = 1L;
           //controller테스트에서 service레이어의 영향을 받고 싶지 않으므로 stubbing처리를 해준다.
           given(articleService.getArticle(articleId)).willReturn(new ArticleDto(articleId, "title", "content"));
    	  // /artices/{id}에 대한 get메소드를 호출했을 때 200ok 및 응답으로 오는 json객체에 대한 값 검증을 andExpext에서 진행한다.
           mvc.perform(get("/articles/" + articleId))
                   .andExpect(status().isOk())
                   .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                   .andExpect(jsonPath("$..['title']").value("title"))
                   .andExpect(jsonPath("$..['content']").value("content"));
       }
    }

    3 . POST방식의 경우 파라미터를 별도로 전달해주어야 하기 때문에 추가 작업이 필요하다. 이때 MultiValueMap이라는 스프링 제공 자료구조를 사용해야 한다.(키의 중복을 허용하는 key-value 형 자료구조) andExpect()구문은 GET방식과 다르지 않다.

    @WebMvcTest(ArticleController.class)
    class ArticleControllerTest{
    	
      @Autowired MockMvc mvc;
      @MockBean ArticleService articleService;
      
      @Test
      @DisplayName("[POST]게시글 생성 테스트(정상)")
      public void createArticleTest() throws Exception{
          MultiValueMap<String, String> param = new LinkedMultiValueMap<>();
          given(articleService.createArticle(any(ArticleDto.class)))
                  .willReturn(new ArticleDto(2L, "title", "content"));
          //request body에 title=제목&content=내용 형식이 담긴다고 가정했을 때 아리와 같이 입력을 해주면 된다.
          param.add("title", "title");
          param.add("content", "content");
    
          mvc.perform(post("/articles/form")
                          .params(param))
                  .andExpect(status().isCreated())
                  .andExpect(content().string("게시글이 생성되었습니다."))
                  .andExpect(header().string("Location", "/articles/2"))
                  .andDo(print());
    
      }
    }
  1. 컨트롤러의 경우 사용자와 직접적으로 상호작용이 있는 레이어이기ㅣ 때문에 다양한 사용자 입력값에 대한 검증이 필요하다. 이런 경우 사용해 볼 수 있는 방법이 @ParameterizedTest이다. @ParameterizedTest를 사용한 경우에는 @Test를 빼주어야 한다. 이번에는 정의한 메소드의 반환값을 테스트 입력으로 사용하는 @MethodSource를 이용하여 테스트를 진행해 보았다. @MethodSource 내에 별도 값을 지정하지 않으면 테스트 메소드와 동일한 이름의 MethodSource를 찾는다.

    @WebMvcTest(ArticleController.class)
    class ArticleControllerTest{
    	
      @Autowired MockMvc mvc;
      @MockBean ArticleService articleService;
      
      @DisplayName("[POST]게시글 생성 테스트(입력값 오류)")
      @MethodSource("createArticleFailTest") //MethodSource를 지정해 주었다.
      @ParameterizedTest(name = "[{index}] message : {2}") //반환하는 인자값을 테스트명으로 사용하기 위해 name인자값을 주었다. {2}는 arguments의 3번째 인자라는 의미이다.
      public void createArticleFailTest(String title, String content, String message) throws Exception {
          MultiValueMap<String, String> param = new LinkedMultiValueMap<>();
          //MethodSource가 반환해주는 arguments가 순서대로 title, content, message에 담기므로 이를 메소드 내에서 적절히 사용하면 된다.
          param.add("title", title);
          param.add("content", content);
    
          mvc.perform(post("/articles/form")
                      .params(param))
                  .andExpect(status().isBadRequest())
                  .andDo(print());
    
      }
    	/*입력으로 사용할 MethodSource는 static 메소드이며 반환값은 Stream<Arguments>로 선언해준다.
        적절한 입력값을 생성한 뒤 Stream.of(arguments(인자값)) 형식으로 반환해준다.
      */
      static Stream<Arguments> createArticleFailTest(){
          StringBuilder sb = new StringBuilder();
          for(int i = 0; i <= 1000; i++){
              sb.append("a");
          }
          String largeContent = sb.toString();
          return Stream.of(
                  arguments(null, null, "입력값 없음"),
                  arguments("title", null, "내용 없음"),
                  arguments(null, "content", "제목 없음"),
                  arguments("title", largeContent, "내용 1000자 초과")
          );
      }
     

3. 정리

  • Controller에 대한 단위테스트를 진행하고 싶다면, 아래와 같이 설정하면 된다.
    - @WebMvcTest(테스트 Controller) 선언
    - 테스트를 수행해 줄 MvcMock
    - Controller가 의존하는 클래스에 @MockBean 선언
    @WebMvcTest(ArticleController.class)
    class ArticleControllerTest{
    	//테스트를 수행해 줄 MockMvc를 선언한다.
       @Autowired MockMvc mvc;
       //컨트롤러에 대한 단위테스트이므로 서비스 코드의 영향을 없애기 위해 가짜 객체를 만들어준다.
       /*MockBean 대상 객체는 스프링 컨테이너에 의해 관리된다.
         따라서 @SpringBootTest와 같이 스프링 컨테이너를 이용하는 경우에는 @MockBean을 사용하고 그렇지 않은 경우에는 @Bean을 사용하면 된다. */
       @MockBean ArticleService articleService;
    }
post-custom-banner

0개의 댓글