[SpringBoot] 7.4.5-6 컨트롤러 테스트
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
컨트롤러 테스트 정리 블로그 끝판왕 (나중에 model과 view test 에 참고)
[SpringBoot] Test(2) MockMvc를 이용해서 테스트하기(@WebMvcTest, @AutoConfigureMockMvc) (tistory.com)