Spring Boot/스프링 부트 핵심 가이드

[SpringBoot] 7.4.5-6 컨트롤러 테스트

nayonsoso 2023. 9. 22. 22:40
Summary :
컨트롤러 테스트는
given - 서비스의 메소드가 특정 값을 리턴한다고 가정 (given / willReturn)
when - 특정 요청이 왔을 때 (mockMvc.perform)
then - 해당하는 형식의 응답인지 (andExpect / andDo)
해당하는 서비스의 메소드가 호출되었는지 (verify) 를 체크한다.

🔍 목차 


7.4.5 스프링 부트에서의 테스트
7.4.6 컨트롤러 객체의 테스트
    7.4.6.1 컨트롤러 테스트 코드의 요소 1 - MockMvc
    7.4.6.2 컨트롤러 테스트 코드의 요소 2 - 어노테이션
    7.4.6.3 컨트롤러 테스트 코드의 요소 3 - verify
    7.4.6.4 컨트롤러 테스트 코드 예시
    7.4.6.5 스프링 시큐리티 인증이 포함된 컨트롤러 테스트

📌 7.4.5 스프링 부트에서의 테스트


스프링 부트를 사용하는 애플리케이션은 단위 테스트를 하기 어려운 경우가 있음
스프링 부트가 자동 지원하는 기능들을 사용하고 있기 때문에 
스프링부트가 완전히 제외된 단위 테스트를 수행하기 어려운 것
➡️ 스프링 부트에서는 레이어별 테스트, 즉 슬라이스 테스트를 함
➡️ 그리고 레이어별 적합한 테스트 방법을 사용해야 함
e.g. 컨트롤러 객체는 웹의 요청,응답을 테스트하기 위해 @WebMvcTest 를 사용

📌 7.4.6 컨트롤러 객체의 테스트


컨트롤러의 핵심 역할 : 
1. 클라이언트로부터 요청을 받아 
2. 요청에 맞은 서비스 컴포넌트로 요청을 전달하고 
3. 그 결괏값을 클라이언트에게 응답하는 역할

따라서 컨트롤러 테스트에서 검증해야 할 것 :
특정 요청이 들어왔을 때 알맞는 서비스의 메소드를 호출했는지 & 의도한 형식이 반환되는지

📌 컨트롤러 테스트 코드의 요소 1 - MockMvc


컨트롤러 테스트 코드의 요소 1 - MockMvc

  • API를 테스트하기 위해 사용되는 객체
  • 서버(서블릿 컨테이너, 톰캣)를 구동시키지 않아도 테스트 가능
  • 정확히는 가상의 MVC 환경에서 모의 HTTP 서블릿을 요청하는 유틸리티 클래스

MockMvc 에서 제공하는 메서드 : perform() 메서드

  • method와 URL을 설정하여 요청을 테스트할 수 있음
  • method를 설정하는 방법 :
    MockMvcRequestBuilders에서 제공하는 GET, POST, PUT, DELETE에 매핑되는 HTTP 메서드를 사용
  • URL을 설정하는 방법 : 
    MockMvcRequestBuilders에서 제공하는 HTTP 메서드의 인자로 URL을 정의
  • perform() 메서드가 반환하는 것 : ResultActions 객체
  • cf. request body를 설정하는 방법 => .content() 와 .contentType() 을 이용 
    이때 content 안에 JSON이 들어가야 하므로 dto 객체를 JSON으로 바꾸기 위한 
    Gson이나 ObjectMapper를 사용함

ResultMatcher 에서 제공하는 메서드 : andExpect() / andDo()

  • andExpect() 메서드를 사용해 결괏값 검증 할 수 있음
    사용법 : andExpect(status().isOk());
  • andDo() 메서드를 사용해 요청과 응답의 전체 내용을 출력할 수 있음
    사용법 : andDo(print());

📌 컨트롤러 테스트 코드의 요소 2 - 어노테이션


@WebMvcTest({테스트 대상 컨트롤러}.class)

  • 웹에서 사용되는 요청과 응답에 대한 테스트를 수행
  • 대상 클래스만 로드해 테스트를 수행하며, 대상 클래스를 추가하지 않으면
    @Controller, @RestController, @ControllerAdvice 등
    컨트롤러 관련 빈객 체가 모두 로드되므로,
    보다 가벼운 테스트를 위해서는 대상 클래스를 적어줘야 함.
  • cf. WebMvcTest 와 SpringBootTest의 차이
    @SpringBootTest는 프로젝트의 전체 빈을 등록
    @WebMvcTest는 필요한 빈만 등록
    따라서 SpringBootTest는 속도가 느리고, 통합 테스트를 할 때 사용
    WebMvcTest는 속도가 빠르고, 슬라이스 테스트를 할 때 사용 

@MockBean

  • 실제 객체가 아닌 Mock(가짜) 객체를 생성해서 주입
  • @MockBean이 선언된 객체는 실제 객체가 아니기 때문에 실제 행위를 수행하지 않으므로, 
    개발자가 동작을 정의해야 함
  • given() - 어떤 메서드가 호출되고 어떤 파라미터가 들어가는지 설정
    willReturn() - 메서드를 통해 어떤 결과를 리턴할 것인지 설정

📌 컨트롤러 테스트 코드의 요소 3 - verify


verify() 메서드는 지정된 메서드가 실행됐는지 검증하는 역할
일반적으로 given()에 정의된 동작과 대응함

📌 컨트롤러 테스트 코드 예시


@WebMvcTest(AccountController.class)
class AccountControllerTest {
    // 목(가짜) 객체
    @MockBean
    private AccountService accountService;

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void successCreateAccount() throws Exception {
        //given - 목객체의 함수, 매개변수, 리턴값 설정
        given(accountService.createAccount(anyLong(), anyLong()))
                .willReturn(AccountDto.builder()
                        .userId(1L)
                        .accountNumber("1234567890")
                        .registeredAt(LocalDateTime.now())
                        .unRegisteredAt(LocalDateTime.now())
                        .build());
        //when
        //then
        mockMvc.perform(post("/account")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(
                        new CreateAccount.Request(anyLong(), anyLong())
                )))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.userId").value(1))
                .andExpect(jsonPath("$.accountNumber").value("1234567890"))
                .andDo(print());
        
		verify(accountService).createAccount(anyLong(), anyLong());
    }


cf. MockMvc의 코드는 테스트 실행과 검증이 합쳐져있어 'When-Then'을 명확히 구분하긴 어려움

📌 cf. 스프링 시큐리티 인증이 포함된 컨트롤러 테스트


SpringSecurity를 적용하면 컨트롤러 테스트에서도 인증이 필요.
이 때 가짜 사용자로 테스트를 실행할 수 있는 방법이 @WithMockUser를 사용하는 것.

WithMockUser는 아래와 같은 속성을 가진 MockUser를 만들고
WithSecurityContextFactory 에서 이를 이용해 Authentication의 principal에 넣는다.
즉, 가상으로 인증된 유저 요청처럼 꾸밀 수 있다.

public @interface WithMockUser {
	String value() default "user";
	String username() default "";
	String[] roles() default { "USER" };
	String[] authorities() default {};
	String password() default "password";
}


커스텀 유저로 테스트하는 경우 :
https://velog.io/@coastby/security-JwtFilter-%EC%B6%94%EA%B0%80%ED%95%98%EA%B3%A0-%ED%85%8C%EC%8A%A4%ED%8A%B8-authentication-%EC%A3%BC%EC%9E%85

 

[security] JwtFilter 추가하고 테스트 (authentication 주입)

SpringSecurity를 적용하면 컨트롤러 테스트에서도 인증이 필요하다. 이 때 가짜 사용자로 테스트를 실행할 수 있는 방법이 @WithMockUser이다. MockUser는 아래와 같은 속성을 가진 MockUser를 만들고 WithSecu

velog.io

컨트롤러 테스트 정리 블로그 끝판왕 (나중에 model과 view test 에 참고)

[SpringBoot] Test(2) MockMvc를 이용해서 테스트하기(@WebMvcTest, @AutoConfigureMockMvc) (tistory.com)

 

[SpringBoot] Test(2) MockMvc를 이용해서 테스트하기(@WebMvcTest, @AutoConfigureMockMvc)

Mock - Mock이라는 단어를 사전에서 찾아보면 '테스트를 위해 만든 모형'을 의미한다. - 테스트를 위해 실제 객체와 비슷한 모의 객체를 만드는 것을 모킹(Mocking)이라고 하며, 모킹한 객체를 메모리

elevatingcodingclub.tistory.com