Mockito 를 사용한 통합 테스트

알파로그·2023년 10월 22일
0
post-custom-banner

🔗 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());
    }

		// DTO 객체를 JSON String 으로 변환하기 위해선 
		// DTO 객체에 기본 생성자가 있어야 가능하다.
    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{
		// given
        Long
                missionId = 1L,
                problemStatusId = 1L;
        String
                title = "code review",
                content = "hello";
        CreateCodeReviewDto dto = toDto(missionId, problemStatusId, title, content);

		// when
        ResultActions result = post(mvc, POST_USER_URL + 
                "/v1/code-review", jwt1, dto);

		// then
        result.andExpect(status().is2xxSuccessful());
    }
}

📍 더 디테일 하게 검증하기

  • API 요청이 200 OK 로 성공했더라도 논리오류가 발생했을 수 있다.
    • 논리오류를 촘촘하게 검증하기 위해선 반환값 또는 Data 를 조회해 의도에 맞게 API 가 작동되었는지 확인해야 한다.
  • ObjectMapper 를 이용해 DTO 를 String 으로 바꿨듯,
    ResultActions 도 API 의 반환 객체로 변환할 수 있다.
    - 변환 코드는 재사용될 경우가 많기때문에 MvcMockRequest 에 구현해줬다.
  • JavaTimeModule 을 등록해 LocalDataTime 직렬화가 안되는 문제를 해결할 수 있다.
public class MockMvcRequest {

	   ...

		// 반환값을 DTO 로 변환
    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);
    }

		// 반환값을 DTO List 로 변환
		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("권한이 없습니다."));
}
profile
잘못된 내용 PR 환영
post-custom-banner

0개의 댓글