스프링 시큐리티를 적용하고 나면 기존에 작성한 테스트 코드에 에러가 발생한다.
기존에는 바로 API를 호출할 수 있어 테스트 코드 역시 바로 API를 호출하도록 구성하였다.
하지만 시큐리티 옵션이 활성화되면 인증된 사용자만 API를 호출할 수 있기 때문에 에러가 발생하는 것이다.
테스트 코드마다 인증한 사용자가 호출한 것처럼 작동하도록 수정해보자.
전체 테스트를 수행해보자.
아래 테스트 코드는 모두 302에러가 발생한다.
hello가_리턴된다()
helloDto가_리턴된다()
Posts_등록된다()
Posts_수정된다()
302 Status Code(리다이렉션 응답)는 요청한 리소스가 임시적으로 이동하였음을 나타낸다. 스프링 시큐리티 설정 때문에 인증되지 않은 사용자의 요청은 이동시키기 때문이다.
java.lang.AssertionError: Status expected:<200> but was:<302>
필요:200
실제 :302
스프링 시큐리티 테스트를 위한 여러 도구를 지원하는 spring-security-test 를 build.gradle에 추가한다.
testImplementation("org.springframework.security:spring-security-test")
hello가_리턴된다(), helloDto가_리턴된다()는 SecurityMockMvcRequestPostProcessors
클래스의 메소드인 oauth2Login()
을 이용하면 된다.
해당 클래스는 OAuth2 뿐만 아니라 스프링 시큐리티에서 지원하는 다양한 인증 처리 방식들을 테스트하기 쉽도록 제공한다.
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login;
...
@Test
public void hello가_리턴된다() throws Exception {
String hello = "hello";
// 변경 후
mvc.perform(get("/hello").with(oauth2Login()))
.andExpect(status().isOk())
.andExpect(content().string(hello));
/* 변경 전
mvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string(hello));
*/
}
@Test
public void helloDto가_리턴된다() throws Exception {
String name = "hello";
int amount = 1000;
// 변경 후
mvc.perform(get("/hello/dto").with(oauth2Login()).param("name", name).param("amount", String.valueOf(amount)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", is(name)))
.andExpect(jsonPath("$.amount", is(amount)));
/* 변경 전
mvc.perform(get("/hello/dto").param("name", name).param("amount", String.valueOf(amount)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", is(name)))
.andExpect(jsonPath("$.amount", is(amount)));
*/
}
(해결) hello가_리턴된다(), helloDto가_리턴된다()
나머지 Posts등록된다(), Posts수정된다() 도 해결해보자.
package com.spring.book.web;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {
...
//MockMvc code start
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void setup(){
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
//MockMvc code end
@Test
@WithMockUser(roles="USER")
public void Posts_등록된다() throws Exception{
//given
String title = "title";
String content = "content";
PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
.title(title)
.content(content)
.author("author")
.build();
String url = "http://localhost:" + port + "/api/v1/posts";
//when
//ResponseEntity<Long> responseEntity = restTemplate.postForEntity(url, requestDto, Long.class);
mvc.perform(post(url)
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(requestDto)))
.andExpect(status().isOk());
//then
//assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
//assertThat(responseEntity.getBody()).isGreaterThan(0L);
List<Posts> all = postsRepository.findAll();
assertThat(all.get(0).getTitle()).isEqualTo(title);
assertThat(all.get(0).getContent()).isEqualTo(content);
}
@Test
@WithMockUser(roles="USER")
public void Posts_수정된다() throws Exception {
//given
Posts savedPosts = postsRepository.save(Posts.builder()
.title("title")
.content("content")
.author("author")
.build());
Long updateId = savedPosts.getId();
String expectedTitle = "title2";
String expectedContent = "content2";
PostsUpdateRequestDto requestDto = PostsUpdateRequestDto.builder()
.title(expectedTitle)
.content(expectedContent)
.build();
String url = "http://localhost:" + port + "/api/v1/posts/" + updateId;
HttpEntity<PostsUpdateRequestDto> requestEntity = new HttpEntity<>(requestDto);
//when
//ResponseEntity<Long> responseEntity = restTemplate.exchange(url, HttpMethod.PUT, requestEntity, Long.class);
mvc.perform(put(url)
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(requestDto)))
.andExpect(status().isOk());
//then
//assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
//assertThat(responseEntity.getBody()).isGreaterThan(0L);
List<Posts> all = postsRepository.findAll();
assertThat(all.get(0).getTitle()).isEqualTo(expectedTitle);
assertThat(all.get(0).getContent()).isEqualTo(expectedContent);
}
}
(1) 임시 사용자 인증 추가
Posts등록된다() 와 Posts수정된다() 에 @WithMockUser(roles="USER")
임시 사용자 인증을 추가하자.
이 어노테이션으로 인해 ROLE_USER 권한을 가진 사용자가 API를 요청하는 것과 동일한 효과를 가진다.
(2) @SpringBootTest에서 MockMvc 사용하기
@WithMockUser는 MockMvc에서만 작동한다. 현재 PostsApiControllerTest 클래스는 @SpringBootTest로만 되어 있으며 MockMvc를 전혀 사용하지 않는다. 코드에 다음 내용을 추가해주자.
@Before
mvc.perform
재실행 결과
(해결) Posts등록된다(), Posts수정된다()
“B. 테스트 코드에 임시로 사용자 인증 추가해주기” 코드 수정 후 405 에러 발생
405 (Http method not supportred) 에러는 컨트롤러에서 동작에 맞는(get,Post, put,delete)는 요청값이나 Url이 없으면 발생하는 에러이다.
게시글 수정하기 동작에서는 put 으로 요청해야 되는데 post로 요청하였다.
구현된 컨트롤러에는 http://localhost:" + port + "/api/v1/posts/" + updateId
url에는 put 요청만 받을 수 있다.
post → put 으로 동작하니 상태 코드가 200으로 정상적으로 나왔다.
mvc.perform(put(url)
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(requestDto)))
.andExpect(status().isOk());
302 에러 참고
405 에러 참고