티스토리 뷰
스프링/스프링부트 RestAPI 프로젝트
spring boot REST API Web 프로젝트 (11 - 2) - JUnit Test (통합 테스트)
구름뭉치 2021. 8. 24. 20:32스프링 부트 REST API WEB 프로젝트
깃헙 링크
https://github.com/choiwoonsik/springboot_RestApi_App_Project/tree/main/restApiSpringBootApp
수행 목록
- 환경구성 및 helloworld 출력
- H2 DB 연동
- Swagger API 문서 연동
- REST API 설계
- RestControllerAdvice를 이용한 통합 예외 처리
- Entity - DTO 분리
- MessageSource를 이용해 예외 메시지 다국화
- JPA Aduting을 이용해 객체 생성시간/수정시간 적용
- 스프링 시큐리티 + Jwt를 이용해서 인증 및 권한 체크
- 스프링 시큐리티 AuthenticationEntryPoint, AccessDenied로 인증 및 인가 예외처리
- Jwt AccessToken + RefreshToken으로 보안성과 사용자 편의성 고도화하기
- JUnit Test (단위 테스트)
- JUnit Test (통합 테스트)
- OAuth 2.0 정리
- OAuth 2.0 카카오 로그인 part.1 Authorization code + Token 발급
- OAuth 2.0 카카오 로그인 part.2 토큰으로 회원 가입 / 로그인
- OAuth 2.0 카카오 로그인 테스트 검증
- 환경별 설정을 위해서profile 분리하기
통합 테스트에 대해서 알아보자
@SpringBootTest
- 실제 운영환경에서 사용될 클래스들을 통합하여 테스트한다.
- 단위 테스트와 같이 기능검증을 위한 것이 아니라 스프링 프레임워크에서 전체적으로 로직이 제대로 동작하는지 검증하기 위해 사용한다.
- 애플리케이션의 설정, 모든 Bean을 로딩하므로 운영환경과 가장 유사하게 테스트할 수 있다.
- 단, 그만큼 느리다.
@SpringBootTest + @AutoConfigureMockMvc : SignController 테스트
@AutoConfigureMockMvc를 사용하면 MockMvc를 간편하게 사용할 수 있게해준다.
회원가입, 로그인에 대한 성공 / 실패 테스트를 만들어보자.
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
public class SignControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserJpaRepo userJpaRepo;
@Autowired
PasswordEncoder passwordEncoder;
@Before
public void setUp() {
userJpaRepo.save(User.builder()
.name("woonsik")
.password(passwordEncoder.encode("password"))
.nickName("woonsik")
.email("email@email.com")
.roles(Collections.singletonList("ROLE_USER"))
.build());
}
@Test
public void 로그인_성공() throws Exception {
String object = objectMapper.writeValueAsString(UserLoginRequestDto.builder()
.email("email@email.com")
.password("password")
.build());
//given
ResultActions actions = mockMvc.perform(post("/v1/login")
.content(object)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON));
//then
actions.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.code").value(0))
.andExpect(jsonPath("$.msg").exists());
}
@Test
public void 회원가입_성공() throws Exception {
//given
long time = LocalDateTime.now().atZone(ZoneId.systemDefault()).toEpochSecond();
String object = objectMapper.writeValueAsString(UserSignupRequestDto.builder()
.email("email@email.com" + time)
.nickName("woonsik")
.name("woonsik")
.password("myPassword")
.build());
ResultActions actions = mockMvc.perform(
post("/v1/signup")
.content(object)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON));
//then
actions.
andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.code").value(0))
.andExpect(jsonPath("$.msg").exists());
}
@Test
public void 회원가입_실패() throws Exception {
//given
String object = objectMapper.writeValueAsString(UserSignupRequestDto.builder()
.name("woonsik")
.email("email@email.com")
.password("password")
.nickName("woonsik")
.build());
//when
ResultActions actions = mockMvc.perform(post("/v1/signup")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(object));
//then
actions.andDo(print())
.andExpect(status().is5xxServerError())
.andExpect(jsonPath("$.success").value(false))
.andExpect(jsonPath("$.code").value(-1002));
}
@Test
public void 로그인_실패() throws Exception
{
//given
String object = objectMapper.writeValueAsString(UserLoginRequestDto.builder()
.email("email@email.com")
.password("wrongPassword")
.build());
//when
ResultActions actions = mockMvc.perform(post("/v1/login")
.content(object)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON));
//then
actions
.andDo(print())
.andExpect(status().is5xxServerError())
.andExpect(jsonPath("$.success").value(false))
.andExpect(jsonPath("$.code").value(-1001));
}
}
4가지 경우 모두 제대로 돌아가는 모습
설명
String object = objectMapper.writeValueAsString(요청 객체)
ObjectMapper를 이용해서 요청을 보낼 객체를 생성해준다.
mockMvc.perform(post("url") //get, post 등등
.content(object) // 요청보내는 @RequestBody
.contentType(MediaType.APPLICATION_JSON) // contentType을 json형식으로 설정
.accept(MediaType.APPLICATION_JSON)); // header를 json형식으로 설정
MockMvc가 수행할 행동을 정의해준다.
perform
- URL을 받아서 GET / POST / PUT / DELETE 등 다양한 메서드를 수행할 수 있다.
- header에 값을 세팅할수 있다. content넣어주기
- AcceptType을 설정해준다 (MediaType.APPLICATION_JSON)
actions.
andDo(print()) // 내용 출력
.andExpect(status().isOk()) // HTTP response Code 200 인지 확인
.andExpect(jsonPath("$.success").value(true)); // json내 키값을 기준으로 비교, success가 true인지
해당 행동을 통해 일어난 결과에 대해 검증한다.
andDo()
- perform요청을 처리한다.
andExpect()
- 검증내용을 체크한다.
- 결과가 200 OK인지 확인 == isOk() / 300 == isMovedPermanantly() / 3xx == is3xxRedirection() 등등 다양한 조건이 가능하다.
- 반환된 Json객체에 대해서도 체크가 가능하다
- .andExpect(jsonPath("$.success").value(false))
andReturn()
- MvcResult 객체로 반환시켜준다.
SpringSecurity 테스트를 위한 @WithMockUser
유저에게 리소스의 사용권한이 있는지 없는지에 따른 테스트를 할 때 용이하다.
USER권한으로만 접근할 수 있는 리소스에 GUEST권한의 유저가 접근하는 경우
@Test
@WithMockUser(username = "mockUser", roles = {"GUEST"})
public void 접근실패() throws Exception {
//given
//when
//then
mockMvc.perform(get("/v1/users"))
.andDo(print())
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/exception/accessDenied"));;
}
}
정상적인 권한으로 접근하는 경우
{
@Test
@WithMockUser(username = "mockUser", roles = {"GUEST", "USER"})
public void 접근성공() throws Exception
{
//given
//when
//then
mockMvc.perform(get("/v1/users"))
.andDo(print())
.andExpect(status().isOk());
}
}
@SpringBootTest + @AutoConfigureMockMvc : UserController 테스트
User 관련 서비스를 처리하기 위해서는 인증된 유저로 요청해야 하므로 MockUser를 이용해서 접근해야한다.
- 따라서 최상단에 @WithMockUser를 달아주었다. roles는 default로 USER이므로 명시하지 않았다.
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
@WithMockUser(username = "mockUser")
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
UserService userService;
@Autowired
UserJpaRepo userJpaRepo;
@Autowired
PasswordEncoder passwordEncoder;
private static int id;
@Before
public void setUp() {
User save = userJpaRepo.save(User.builder()
.name("woonsik")
.password(passwordEncoder.encode("password"))
.nickName("woonsik")
.email("email@email.com")
.roles(Collections.singletonList("ROLE_USER"))
.build());
id = Math.toIntExact(save.getUserId());
}
@Test
public void 회원조회_이메일() throws Exception {
//then
ResultActions actions = mockMvc.perform(
get("/v1/user/email/{email}", "email@email.com")
.param("lang", "en"));
actions
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.email", is("email@email.com")))
.andExpect(jsonPath("$.data.name", is("woonsik")))
.andReturn();
}
@Test
public void 회원조회_userId() throws Exception {
//given
ResultActions actions = mockMvc.perform(get("/v1/user/id/{id}", id)
.param("lang", "en"));
//when
//then
actions.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.userId", is(id)))
.andExpect(jsonPath("$.data.email", is("email@email.com")))
.andExpect(jsonPath("$.data.name", is("woonsik")));
}
@Test
public void 전체_회원조회() throws Exception {
//then
mockMvc.perform(get("/v1/users"))
.andDo(print())
.andExpect(status().isOk());
}
@Test
public void 회원수정() throws Exception {
//given
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("userId", String.valueOf(id));
params.add("nickName", "afterNickName");
//when
ResultActions actions = mockMvc.perform(put("/v1/user")
.params(params));
//then
actions
.andDo(print())
.andExpect(jsonPath("$.success", is(true)))
.andExpect(jsonPath("$.code", is(0)))
.andExpect(jsonPath("$.data", is(id)));
}
@Test
public void 회원삭제() throws Exception {
//given
//when
ResultActions actions = mockMvc.perform(delete("/v1/user/{id}", id));
//then
actions
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.success", is(true)))
.andExpect(jsonPath("$.code", is(0)))
.andExpect(jsonPath("$.msg").exists());
}
}
mockMvc perform을 작성할 때
- @PathVariable의 경우에는 (/url/{var}, var) 식으로 넣어주면된다. 여러개일경우 /{a}/{b}, a, b .. 로 반복해서 하면된다.
- @RequestParam의 경우에
-
멀티 param이 필요한경우 위와같이 map객체를 생성후 필요한 값들을 매핑해준후 params( "만든 params" ) 로 넣어주면된다.MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
-
단일 param의 경우 바로 key-value 형태로 넣으면 된다.param("lang", "en")
-
jsonPath로 값을 접근할 때
- json객체의 depth가 추가되면 {"$.변수.변수"} 이렇게 들어가주면 접근할 수 있다.
반응형
'스프링 > 스프링부트 RestAPI 프로젝트' 카테고리의 다른 글
Comments
반응형
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday