스프링 부트와 AWS로 혼자 구현하는 웹 서비스
: 인텔리제이, JPA, JUnit 테스트, 그레이들, 소셜 로그인, AWS 인프라로 무중단 배포까지
이동욱 저
Table of Content
게시글 등록 테스트코드의 오류
스프링 시큐리티 적용 전에 정상 작동(200 Status Code)하던 테스트 코드가 리다이렉션(302 Status Code) 응답이 나타나는 오류가 있다.
인증되지 않은 사용자의 요청
스프링 시큐리티 설정은 인증되지 않은 사용자의 요청은 이동시킨다. 따라서 이런 API 요청은 임의로 인증된 사용자를 추가(모의객체)를 통해 요청 API만 테스트 할 수 있도록 수정해야 한다.(해당 테스트 코드는 API 요청의 테스트 의도이기 때문)
build.gradle에 스프링 시큐리티 테스트 의존성 추가
스프링 시큐리티 테스트를 위한 여러 도구를 지원하는 spring-security-test를 추가한다.
dependencies {
...
testImplementation 'org.springframework.security:spring-security-test'
...
}
Java
복사
PostApiControllerTest.java에 임의(모의) 사용자 인증 추가
@WithMockUser(roles = "USER") 을 통해서 인증된 임의 사용자를 추가하여 해당 테스트를 실행할 수 있다.
@Test
@WithMockUser(roles = "USER")
public void Posts_등록된다() throws Exception{
//given
String title = "title";
String content = "content";
PostsSaveRequestDto requestDto = Pos
...
@Test
@WithMockUser(roles = "USER")
public void Posts_수정된다() throws Exception{
//given
Posts savedPosts = postsRepository.save(Posts.builder()
.title("title")
Java
복사
@SpringBootTest에서 MockMvc를 사용하도록 변경
위 작성한 @WithMockUser(roles = "USER") 어노테이션은 MockMvc 환경에서만 작동하기 때문에 현재는 작동하지 않는다.
아래 처럼 WebApplicationContext 를 추가해주고 MockMvc 를 테스트 이전에 실행 될 수 있도록 @BeforeEach 어노테이션으로 스프링 시큐리티를 적용 시키는 것을 정의해준다.
이제 MockMvc를 통해서 post, put 요청을 보내면 시큐리티가 적용된 환경에서 MockUser를 기반으로 테스트가 실행되게 된다.
package com.citefred.ldwspring.web;
import com.citefred.ldwspring.domain.posts.Posts;
import com.citefred.ldwspring.domain.posts.PostsRepository;
import com.citefred.ldwspring.web.dto.PostsSaveRequestDto;
import com.citefred.ldwspring.web.dto.PostsUpdateRequestDto;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class PostsApiControllerTest {
@LocalServerPort
private int port;
...
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@BeforeEach
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
...
@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
mvc.perform(post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(new ObjectMapper().writeValueAsString(requestDto)))
.andExpect(status().isOk());
//then
List<Posts> postsLists =postsRepository.findAll();
assertThat(postsLists.get(0).getTitle()).isEqualTo(title);
assertThat(postsLists.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;
//when
mvc.perform(put(url)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(new ObjectMapper().writeValueAsString(requestDto)))
.andExpect(status().isOk());
//then
List<Posts> postsLists =postsRepository.findAll();
assertThat(postsLists.get(0).getTitle()).isEqualTo(expectedTitle);
assertThat(postsLists.get(0).getContent()).isEqualTo(expectedContent);
}
}
Java
복사
게시글 수정 부분에서의 DTO 생성자 오류 해결
게시글 작성과 수정은 거의 동일한 메소드고 REST 요청만 POST vs PUT의 차이만 있는 수준이다. 따라서 위 테스트 코드로 MockUser도 동일하게 삽입했지만 수정 부분에서 아래와 같은 오류가 발생했다.
Type definition error로 PostsUpdateRequestDto 와 관련된 오류였다. 해당 문제에 대한 검색을 통해 관련 문제에 대한 해결방법을 찾아보기 시작했다. 우선 Dto와 관련있는 문제이기 때문에 가장 근접한 해결법을 쉽게 찾을 수 있었다.
해당 문제의 원인은 domain의 생성자를 인식하지 못해서 생기는 에러 인 것으로 나타났다.
따라서 PostsUpdateRequestDto 를 열어본 결과 @Getter 어노테이션 외에 별다른 설정은 없었고, 그럼 정상 작동하는 PostsSaveRequestDto는 어떻게 차이나는지 열어본 결과 @NoArgsConstructor 으로 기본 생성자를 작성해주고 있는 차이가 있었다.
따라서 기본생성자를 생성해주는 @NoArgsConstructor를 PostsUpdateRequestDto 에 추가하면서 해당 오류를 해결 할 수 있었다.
package com.citefred.ldwspring.web.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public class PostsUpdateRequestDto {
private String title;
private String content;
@Builder
public PostsUpdateRequestDto(String title, String content){
this.title = title;
this.content = content;
}
}
Java
복사
Related Posts
Search