티스토리 뷰
1. 사건 발달
스프링 시큐리티 환경에서 MVC를 테스트하기 위해서는 요청 헤더에 CSRF 토큰이 필요하다. 따라서 아래 코드와 같이 perform() 시에 요청 헤더에 스프링 시큐리티가 생성한 CSRF 토큰을 포함할 수 있다.
근데 테스트 코드를 작성할 때마다 CSRF 토큰을 일일이 추가하는 것보단, 테스트 실행 전에 자동으로 추가할 수 있지 않을까?
@WithMockUser
@WebMvcTest(controllers = SectionController.class)
class SectionControllerTest {
@Test
@DisplayName("신규 회고카드를 등록한다.")
void createSection() throws Exception {
//given
CreateSectionRequest request = CreateSectionRequest.builder()
.retrospectiveId(1L)
.templateSectionId(2L)
.sectionContent("내용")
.build();
//when //then
mockMvc.perform(
post("/sections")
.content(objectMapper.writeValueAsString(request))
.contentType(APPLICATION_JSON)
.with(csrf())
)
.andDo(print())
.andExpect(status().isCreated())
.andExpect(jsonPath("$.code").value("201"))
.andExpect(jsonPath("$.message").doesNotExist());
}
}
2. 요청 헤더에 CSRF 토큰 자동으로 추가하기
@BeforeEach는 JUnit5에서 제공하는 애너테이션으로, 각 테스트가 실행되기 전에 호출된다. 이 애너테이션을 사용하여 모든 테스트가 실행되기 전에 CSRF 토큰을 추가하도록 해보자.
- (1) MockMvcBuilders는 MockMvc 객체를 생성할 때 사용되는 유틸리티 클래스이다.
- (2) 실제 웹 애플리케이션 컨텍스트(WebApplicationContext)를 사용하여 MockMvc 객체를 생성한다.
- WebApplicationContext는 테스트할 때, Spring의 실제 웹 애플리케이션 환경을 모방하기 위해 사용된다.
- (3) Spring Security 설정을 MockMvc에 적용한다.
- springSecurity()는 SecurityMockMvcConfigurers의 메서드로, Spring Security의 필터 체인이 MockMvc 요청에 대해 적용되도록 설정한다.
- 실제 웹 애플리케이션에서처럼 인증, 인가, CSRF 보호 등의 보안 기능을 테스트할 수 있게 된다.
- (4) POST 요청의 모든 엔드포인트에 대하여 CSRF 토큰을 자동으로 포함한다.
- defaultRequest()는 모든 테스트에서 사용될 요청 정보를 설정한다.
- post(), get() 이런 식으로 HTTP 메서드를 지정할 수 있다.
- ex) post("/**") : 모든 경로("/**")에 대해 POST 요청을 대상으로 설정을 수행한다.
@WithMockUser
@WebMvcTest(controllers = SectionController.class)
class SectionControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private WebApplicationContext applicationContext;
@BeforeEach
public void setUp() {
mockMvc = MockMvcBuilders // (1)
.webAppContextSetup(applicationContext) // (2)
.apply(springSecurity()) // (3)
.defaultRequest(post("/**").with(csrf())) // (4)
.build();
}
}
3. 결론
@BeforeEach에서 MockMvc 설정을 통해 매번 테스트 코드에 with(csrf())를 수동으로 추가하는 번거로움을 줄일 수 있다.
@WithMockUser
@WebMvcTest(controllers = SectionController.class)
class SectionControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private WebApplicationContext applicationContext;
@Autowired
private ObjectMapper objectMapper;
@BeforeEach
public void setUp() {
mockMvc = MockMvcBuilders
.webAppContextSetup(applicationContext)
.apply(springSecurity())
.defaultRequest(post("/**").with(csrf()))
.build();
}
@Test
@DisplayName("신규 회고카드를 등록한다.")
void createSection() throws Exception {
//given
CreateSectionRequest request = CreateSectionRequest.builder()
.retrospectiveId(1L)
.templateSectionId(2L)
.sectionContent("내용")
.build();
//when //then
mockMvc.perform(
post("/sections")
.content(objectMapper.writeValueAsString(request))
.contentType(APPLICATION_JSON)
)
.andDo(print())
.andExpect(status().isCreated())
.andExpect(jsonPath("$.code").value("201"))
.andExpect(jsonPath("$.message").doesNotExist());
}
}