모르지 않다는 것은 아는것과 다르다.

Junit

Controller 단위테스트

채마스 2022. 2. 27. 01:38

@WebMvcTest

  • MVC를 위한 테스트, 컨트롤러가 예상대로 동작하는지 테스트하는데 사용된다.
  • 웹상에서 요청과 응답에 대해 테스트할 수 있음.
  • 시큐리티, 필터까지 자동으로 테스트하며, 수동으로 추가/삭제 가능.
  • @SpringBootTest 어노테이션보다 가볍게 테스트할 수 있음.
  • @WebMvcTest 어노테이션을 사용시 다음 내용만 스캔 하도록 제한한다.
    • @Controller
    • @ControllerAdvice
    • @JsonComponent
    • Converter
    • GenericConverter
    • Filter
    • HandlerInterceptor
    • WebMvcConfigurer
    • HandlerMethodArgumentResolver
  • MockBean, MockMVC를 자동 구성하여 테스트 가능하도록 한다.
  • Spring Security의 테스트도 지원 한다. -> 로그인, 로그아웃, 세션, 필터까지 테스트할 수 있다.
  • @WebMvcTest를 사용하기 위해 테스트할 특정 컨트롤러 클래스를 명시 하도록 한다.
  • 예시는 아래와 같다.
@WebMvcTest(TestRestController.class) 
@Slf4j 
class TestRestControllerTest { 

    @Autowired MockMvc mvc; 

    @MockBean 
    private TestService testService;

    protected MediaType contentType =
            new MediaType(MediaType.APPLICATION_JSON.getType(),
                    MediaType.APPLICATION_JSON.getSubtype(),
                    StandardCharsets.UTF_8); 

    @Test void getListTest() throws Exception { 
        //given 
        TestVo testVo = TestVo.builder() 
            .id("hyunwook") 
            .name("현욱") 
            .build(); 

        //given 
        given(testService.selectOneMember("hyunwook")) 
            .willReturn(testVo); 

        //when 
        final ResultActions actions = mvc.perform(get("/testValue2") 
                .contentType(contentType)) 
                .andDo(print()); 

        //then 
        actions 
            .andExpect(status()
            .isOk()) 
            .andExpect(content().contentType(contentType)) 
            .andExpect(jsonPath("$.name", is("현욱"))) 
            .andDo(print()); 
    } 
}
  • given에서 해당 객체를 생성 처리한다.
  • when에서 목객체를 기반으로 미리 정의된 객체를 반환 처리한다.
    • 선언해둔 contentType 을 지정해 준다.
  • then에서 해당 객체의 응답값을 검사 처리한다.



코드 예시

@WebMvcTest(DMakerController.class)
@MockBean(JpaMetamodelMappingContext.class)
class DMakerControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private DMakerService dMakerService;

    protected MediaType contentType =
            new MediaType(MediaType.APPLICATION_JSON.getType(),
                    MediaType.APPLICATION_JSON.getSubtype(),
                    StandardCharsets.UTF_8);

    @Test
    void getAllDevelopers() throws Exception {
        //given
        DeveloperDto seniorBEDeveloper = DeveloperDto.builder()
                .developerSkillType(DeveloperSkillType.BACK_END)
                .developerLevel(DeveloperLevel.SENIOR)
                .memberId("memberId")
                .build();
        DeveloperDto juniorFEDeveloper = DeveloperDto.builder()
                .developerSkillType(DeveloperSkillType.FRONT_END)
                .developerLevel(DeveloperLevel.JUNIOR)
                .memberId("memberId2")
                .build();
        given(dMakerService.getAllEmployedDevelopers())
                .willReturn(Arrays.asList(seniorBEDeveloper, juniorFEDeveloper));

        //when
        //then
        mockMvc.perform(get("/developers").contentType(contentType))
                .andExpect(status().isOk())
                .andExpect(
                        jsonPath("$.[0].developerSkillType",
                                is(DeveloperSkillType.BACK_END.name())))
                .andExpect(
                        jsonPath("$.[0].developerLevel",
                                is(DeveloperLevel.SENIOR.name())))
                .andExpect(
                        jsonPath("$.[1].developerSkillType",
                                is(DeveloperSkillType.FRONT_END.name())))
                .andExpect(
                        jsonPath("$.[1].developerLevel",
                                is(DeveloperLevel.JUNIOR.name())));
    }

    @Test
    void testErrorMessage() throws Exception {
        //given
        given(dMakerService.getAllEmployedDevelopers())
                .willThrow(new DMakerException(DMakerErrorCode.NO_DEVELOPER));

        //when
        //then
        mockMvc.perform(get("/developers").contentType(contentType))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(
                        jsonPath("$.errorCode",
                                is(DMakerErrorCode.NO_DEVELOPER.name())));

    }
}

 

@WebMvcTest 장점

  • WebApplication 관련된 Bean들만 등록하기 때문에 통합 테스트보다 빠르다.
  • 통합 테스트를 진행하기 어려운 테스트를 진행 가능하다.
  • ex) 결제 모듈 API를 콜하며 안되는 상황에서 Mock을 통해 가짜 객체를 만들어 테스트 가능.



@WebMvcTest 단점

  • 요청부터 응답까지 모든 테스트를 Mock 기반으로 테스트하기 때문에 실제 환경에서는 제대로 동작하지 않을 수 있다.



@WebFluxTest

  • Spring WebFluex component에 초점을 맞춰 Spring WebFlux test에 사용되는 annotation 이다.
  • 전체 auto-configuration을 실행하지 않는 대신 WebFlux와 연관된 configuration만 실행한다.
    • @Controller, @ControllerAdvice, @JsonComponent, Converter/GenericConverter, WebFluxConfigurer bean을 생성하고 적재한다.
    • 하지만 @Component, @Service, @Repository bean은 생성되지 않는다.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SampleControllerTest {

    @Autowired
    WebTestClient webTestClient;

    @MockBean 
    SampleService mockSampleService;

    @Test
    public void helloTest() throws Exception {
        when(mockSampleService.getName()).thenReturn("dave"); 

        webTestClient.get().uri("/hello").exchange()
                .expectStatus().isOk()  
                .expectBody(String.class).isEqualTo("hello dave");  
    }
}
  • SpringMvcWebFlux에 추가된 RestClient 중 하나로 asynchronous하게 동작하여 request에 대한 response가 도착할 때 callback을 실행할 수 있습니다.
  • 비동기 연결방식으로 동작하기 때문에 테스트 코드로 WebClient와 동일한 API를 사용할 수 있다.
  • WebTestClient를 사용하려면 project에 spring-boot-starter-webflux 의존성을 추가해야 한다.




REFERENCES

'Junit' 카테고리의 다른 글

Mockito  (0) 2022.02.27
Junit5 애노테이션 비교  (0) 2022.02.27
# Junit 5 기본 애노테이션  (0) 2022.02.27
Bad Request 처리하기  (0) 2022.02.27
Assertion  (0) 2022.02.27