🔗 Mockito 환경설정
✏️ 테스트 객체 세팅
@SpringBootTest
- 테스트 코드를 실행하기 전에 컨테이너를 실행해 실제 앱이 실행될 때 처럼 컨피그 객체를 빈으로 등록해 컨테이너에 저장한다.
@AutoConfigureMockMvc
- MockMvc 를 설정해 HTTP 요청을 테스트 하는데 사용되는 어노테이션
- 만약 외부 api 요청이 필요하다면
@BeforeEach
에 목킹을 하면 된다.
@DisplayName("코드리뷰 게시물 작성 통합 테스트")
@SpringBootTest
@Transactional
@AutoConfigureMockMvc
class PostCreateController_writeCodeReviewTest extends TestData {
@Autowired MockMvc mvc;
@Autowired PostQueryUseCase postQueryUseCase;
@BeforeEach
void setup() {
}
@Test
@DisplayName("코드리뷰 게시물 등록 성공")
void no1() {
}
}
📍 Test 용 데이터 분리
- API 를 검증하기 위해서 url 경로와 JWT 가 필요한데
TestData
객체를 상속시켜 데이터 선언 코드를 깔끔하게 분리시켜줬다.
public class TestData {
@Value("${custom.mapping.post.web_pub}")
public String POST_PUBLIC_URL;
@Value("${custom.mapping.post.web_usr}")
public String POST_USER_URL;
@Value("${custom.mapping.mission.web_pub}")
public String MISSION_PUBLIC_URL;
@Value("${custom.mapping.comment.web_usr}")
public String COMMENT_USER_URL;
@Value("${custom.jwt.test1}")
public String jwt1;
@Value("${custom.jwt.test2}")
public String jwt2;
@Value("${custom.jwt.test3}")
public String jwt3;
}
📍 MockMvc 커스텀 객체 생성
- API 요청을 보낼 때 불필요한 코드가 너무 길어서 간편하게 보낼 수 있도록 커스텀 객체를 생성했다.
- POST 뿐 아니라 RUD 도 이 방식으로 구현할 수 있고,
PathValiable 의 경우 Object reqDto 대신 Long 이나 String 으로 파라미터를 변경해 오버로딩 하면 된다.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
public class MockMvcRequest {
public static ResultActions post(MockMvc mvc, String url, String jwt, Object reqDto) throws Exception {
String dto = toJasonString(reqDto);
return mvc.perform(MockMvcRequestBuilders
.post(url)
.contentType(APPLICATION_JSON)
.header("Authorization", jwt)
.content(dto)
).andDo(print());
}
private static String toJasonString(Object reqDto) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(reqDto);
}
}
✏️ 통합 테스트 작성
📍 200 OK 검증
- 아래와 같이 검증과 관련없는 코드, 중요하지 않는 코드는 별도로 분리하고
순수한 검증 로직만 남겨둘 수 있게 되었다.
import static com.baeker.Community.global.testUtil.MockMvcRequest.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DisplayName("코드리뷰 게시물 작성 통합 테스트")
@SpringBootTest
@Transactional
@AutoConfigureMockMvc
class PostCreateController_writeCodeReviewTest extends TestData {
@Autowired MockMvc mvc;
@Autowired PostQueryUseCase postQueryUseCase;
@Test
@DisplayName("코드리뷰 게시물 등록 성공")
void no1() throws Exception{
Long
missionId = 1L,
problemStatusId = 1L;
String
title = "code review",
content = "hello";
CreateCodeReviewDto dto = toDto(missionId, problemStatusId, title, content);
ResultActions result = post(mvc, POST_USER_URL +
"/v1/code-review", jwt1, dto);
result.andExpect(status().is2xxSuccessful());
}
}
📍 더 디테일 하게 검증하기
- API 요청이 200 OK 로 성공했더라도 논리오류가 발생했을 수 있다.
- 논리오류를 촘촘하게 검증하기 위해선 반환값 또는 Data 를 조회해 의도에 맞게 API 가 작동되었는지 확인해야 한다.
ObjectMapper
를 이용해 DTO 를 String 으로 바꿨듯,
ResultActions 도 API 의 반환 객체로 변환할 수 있다.
- 변환 코드는 재사용될 경우가 많기때문에 MvcMockRequest 에 구현해줬다.
JavaTimeModule
을 등록해 LocalDataTime 직렬화가 안되는 문제를 해결할 수 있다.
public class MockMvcRequest {
...
public static <T> T toResDto(ResultActions result, Class<T> data) throws UnsupportedEncodingException, JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
MvcResult mvcResult = result.andReturn();
String content = mvcResult.getResponse().getContentAsString();
return mapper
.registerModule(new JavaTimeModule())
.readValue(content, data);
}
public static <T> List<T> toList(ResultActions result, Class<T> data) throws UnsupportedEncodingException, JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
MvcResult mvcResult = result.andReturn();
String content = mvcResult.getResponse().getContentAsString();
CollectionType dataType = mapper
.getTypeFactory()
.constructCollectionType(List.class, data);
return mapper
.registerModule(new JavaTimeModule())
.readValue(content, dataType);
}
}
- 이 방법으로 반환값의 내용도 디테일하게 검증할 수 있다.
- 이 방법 이외에 Repository 에서 api 로 저장한 data 를 조회하는 방법도 있다.
@Test
@DisplayName("코드리뷰 게시물 등록 성공")
void no1() throws Exception{
Long
missionId = 1L,
problemStatusId = 1L;
String
title = "code review",
content = "hello";
CreateCodeReviewDto dto = toDto(missionId, problemStatusId, title, content);
ResultActions result = post(mvc, POST_USER_URL +
"/v1/code-review", jwt1, dto);
result.andExpect(status().is2xxSuccessful());
CodeReviewDto resDto = MockMvcRequest.toResDto(result, CodeReviewDto.class);
assertThat(resDto.getId()).isEqualTo(1L);
assertThat(resDto.getTitle()).isEqualTo(title);
assertThat(resDto.getContent()).isEqualTo(content);
assertThat(resDto.getProblemStatusId()).isEqualTo(problemStatusId);
}
📍 실패할 경우 검증하기
- 요청이 정상적일 경우 이외에 잘못된 요청을 보낼 경우도 검증해야 한다.
jsonPatch
를 사용하면 반환값의 특정 필드를 조회할 수 있다.
@Test
@DisplayName("게시글 수정 권한이 없는 경우")
void no2() throws Exception {
Long postId = createCodeReview(mvc, POST_USER_URL, 1, jwt1);
ModifyPostDto dto = new ModifyPostDto(postId, "modify title", "modify content");
ResultActions result = patch(mvc, POST_USER_URL +
"/v1/post", jwt2, dto);
result
.andExpect(status().isBadRequest())
.andExpect(jsonPath("errorMsg").value("권한이 없습니다."));
}