[RestAPI 개발기] 11. 통합테스트

편준민's avatar
May 15, 2025
[RestAPI 개발기] 11. 통합테스트
💡
모든 개발에는 서비스 전에 통합 테스트를 해봐야한다.

1️⃣ 로컬테스트

  • 개발이 다 완료 되었다면 본인의 컴퓨터로 모든 것을 테스트 해봐야한다. 모든 기능이 정상 작동을 한다면 서버에서 테스트를 해야한다.

2️⃣ 서버테스트

1.서비스가 환경 세팅

💡
서비스를 하기 위해서는 현재 우리가 개발한 컴퓨터로는 서비스를 하지 못한다. 서비스가 가능한 컴퓨터는 필수 조건이 2가지가 있다.
  • 공인IP - 사용자들이 인터넷에서 직접 접근 가능한 IP 주소이다. 공인 IP는 개인 컴퓨터에는 없고 공유기에 공인 IP가 있기 때문에 개인 컴퓨터로는 서비스를 할 수 없다
  • 고정IP - 컴퓨터나 장치가 재시작되거나 시간이 지나도 IP가 일정한 주소가 필요하다.
필수 조건이 2가지가 있다면 권장하는 2가지의 조건도 있다.
  • 무료 OS - 유료 OS를 쓰게 되면 라이선스 비용이 많이 든다. 이후 유지 보수 등 업데이트로 인해서 계약 비용도 들 수 있기 때문에 무료 OS의 사용을 권장한다. (예시로 Linux가 있다.)
  • CLI 환경 - CLI환경이란 사용자가 키보드로 명령어만을 입력해서 컴퓨터를 조작하는 것이다. 마우스로 폴더를 더블 클릭이라던지, 우클릭 이런 것이 없는것이다. 그래픽 아이콘과 화면을 이용하는것은 GUI환경이다. CLI환경을 사용 할 시에는 자원 사용량이 적어서, GUI보다 효율적이다. GUICLI가 같은 컴퓨터를 활용 할 때에 GUI는 100 중 50 정도는 그래픽을 담당하기 때문에 50 정도 밖에 리소스를 사용하지 못한다. 하지만 CLI는 100 중 10~20정도만 사용하기 때문에 더욱 많은 리소스를 사용할 수있다.

2. 서비스 서버

💡
서비스가 가능한 컴퓨터가 생겼다면 우리는 이제 로컬 테스트를 끝마친 코드를 서비스컴퓨터에 코드를 옮겨서 테스트 해봐야한다. 서비스 컴퓨터와 로컬 컴퓨터와의 환경이 다르기 때문에 많은 오류가 생길 것이다. 서비스컴퓨터에서 테스트를 할 때에 많은 어려움이 있는데 그 중 2가지를 알아보겠다.
  • POSTMAN으로 테스트가 힘들다.
    • 우리는 RestAPI를 이용하여 만들었기 때문에 화면이 없다. 즉 화면의 버튼을 클릭하면서 테스트를 해 볼 수 없다는 것이다. 그렇다면 POSTMAN을 이용하여 직접 URL을 입력하고 해야하는데, 만약 오류가 날 경우에는 우리는 이 서버에서 바로 고칠 수 있는 것이 아니라, 로컬 컴퓨터에서 고치고 이후 다시 서비스컴퓨터로 코드를 옮겨야한다.
  • 기존 테스트가 끝나고 서비스 중에 업데이트 사항이 생겼을 경우
    • 테스트가 모두 끝나고 서비스를 하고 있었다. 하지만 우리는 여기서 계속 멈춰 있는게 아니고 더 좋은 앱, 웹을 위하여 업데이트를 해야한다. 더 좋은 기능을 개발하여 업데이트를 해야하는데, 업데이트를 하려면 서비스를 잠시 중단시키고, 업데이트를 해줘야한다. 이러한 서비스 중단을 방지해야한다.
위 2가지를 해결하기 위해서는 우리는 로컬컴퓨터에서 통합테스트 코드를 만들 것이다.
notion image

3️⃣ 테스트 코드

1. 테스트 코드를 사용하는 이유

💡
로컬 서버에서는 잘 돌아가지만 서비스 서버에서는 서로 다른 환경에서 실행하는 것이기 때문에, 서비스 서버에서 정상 작동이 되지 않을 수 있다. 그리고 서비스 서버가 1개가 아니라 무수히 많을 경우에는 무수히 많은 서버를 하나 하나 테스트 해봐야한다. 그러하면 시간이 너무 오래걸린다.
하지만 테스트 코드를 사용하게 되면 각 서버에 빌드 될 때 자동으로 테스트 코드를 실행하고, 테스트 코드에서 오류가 있으면 빌드를 중단한다. 오류가 없으면 정상 작동을 한다는 것이기 때문에 알아서 빌드 된다. 이렇게 테스트 코드를 이용하면 오류가 있으면 ‘아! 오류가 있구나’ 하면서 서비스 서버를 원복 시키지 않고 바로 로컬 컴퓨터에서 테스트 코드만 수정하면 된다.

2. 테스트 코드

💡
  • 가짜 환경을 띄우는 어노테이션
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
  • 가짜 환경을 실행 해볼 수 있는 어노테이션
@AutoConfigureMockMvc
  • 모든 테스트는 테스트가 끝나면 롤백이 된다.
  • 재활용이 가능한 함수여도 의존하는게 많은 경우에는 독립적으로 만드는게 좋다. 독립적으로 만들지 않으면 하나의 함수를 고치면 의존하는 모든 것들도 고쳐야 하기 때문이다.
  • JSON의 가장 최상의 객체는 $로 표현한다. jsonPath()에서 $를 사용하여 꺼내 볼 수 있다.
@AutoConfigureMockMvc // MockMvc 클래스가 IOC로드 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) public class UserControllerTest { @Autowired private ObjectMapper om; @Autowired private MockMvc mvc; @Test public void join_test() throws Exception { // given (가짜데이터) UserRequest.JoinDTO reqDTO = new UserRequest.JoinDTO(); reqDTO.setUsername("haha"); reqDTO.setPassword("1234"); reqDTO.setEmail("haha@nate.com"); String requestBody = om.writeValueAsString(reqDTO); // JAVA 오브젝트를 JSON 오브젝트로 바꿈 // System.out.println(requestBody); // when (테스트 실행) ResultActions actions = mvc.perform( // Exception으로 미리 알려주는 에러를 잡음 MockMvcRequestBuilders.post("/join").content(requestBody).contentType(MediaType.APPLICATION_JSON) ); // eye (결과 눈으로 검증) String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody); // then (결과 코드로 검증) actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.id").value(4)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.username").value("haha")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.email").value("haha@nate.com")); } }
테스트 결과
notion image
notion image
notion image

4️⃣통합테스트

💡
각각의 함수들을 전부 테스트를 해보았다면 이제는 이것을 한 번에 테스트를 해보아야 한다.
  • 통합테스트 방법
    • 테스트클래스가 모여있는 파일 또는 test 파일을 우클릭하여 RUN
notion image
통합테스트를 하는 이유는 각자 따로 실행했을 때에는 전부 오류가 없이 실행 되지만, 순서에 따라서 통합테스트를 할 경우에 오류가 날 수 있다.
예시로는 BoardController에서 똑같으 boardId가 20인 게시글을 조회하고 수정을 할 것 인데, 수정이 먼저 되버리면 boardId가 20인 게시글을 조회하게 되면 수정된 게시글이 조회되기 때문에 코드로 검증하는 과정에서 오류가 난다.
어노테이션@Order을 이용하여서 테스트 코드가 실행되는 순서를 정할 수도 있다. 하지만 이 방법은 좋지 않다. 왜냐하면 테스트의 원칙 중 하나는 독립성인데, 순서에 의존해버리면 독립성이 위배된다. 이후 여러가지 이유도 있다. 유지보수가 어렵고, 병렬 실행이 어려워진다. 또 실제 운영 환경과의 괴리가 생긴다. 그래서 순서의 의존하지 않고 테스트를 짜야한다.
그럼 어떻게 해야하냐? 데이터가 추가가 되거나, 수정되거나 한 것을 테스트가 끝나는 즉시 롤백 시키는 것이다. 테스트가 끝났을 때 롤백을 하면 다른 테스트에는 영향을 끼치지 않는다. 테스트가 끝났을 때 롤백을 하는 방법도 여러가지이지만 가장 쉬운 @Transactional이다. 해당 어노테이션만 붙이면 테스트가 끝날 때 바로 롤백을 해주기 때문에 순서에 의존하지 않고 테스트를 할 수 있다.

5️⃣최종 테스트코드

1. UserController 테스트

package shop.mtcoding.blog.integre; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.transaction.Transactional; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import shop.mtcoding.blog._core.util.JwtUtil; import shop.mtcoding.blog.user.User; import shop.mtcoding.blog.user.UserRequest; import static org.hamcrest.Matchers.matchesPattern; // 컨트롤러 통합 테스트 @Transactional @AutoConfigureMockMvc // MockMvc 클래스가 IoC에 로드 | RestTemplate -> 자바스크립트의 fetch와 동일, 진짜 환경에 요청 가능 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) // MOCK -> 가짜 환경을 만들어 필요한 의존관계를 다 메모리에 올려서 테스트 public class UserControllerTest { @Autowired private ObjectMapper om; // json <-> java Object 변환 해주는 객체. IoC에 objectMapper가 이미 떠있음 @Autowired private MockMvc mvc; // 가짜 환경에서 fetch 요청하는 클래스 | RestTemplate -> 자바스크립트의 fetch와 동일, 진짜 환경에 요청 가능 private String accessToken; @BeforeEach public void setUp() { // 테스트 시작 전에 실행할 코드 System.out.println("setUp"); User ssar = User.builder() .id(1) .username("ssar") .build(); accessToken = JwtUtil.create(ssar); } @AfterEach public void tearDown() { // 끝나고 나서 마무리 함수 // 테스트 후 정리할 코드 System.out.println("tearDown"); } @Test public void join_test() throws Exception { // 이 메서드를 호출한 주체에게 예외 위임 -> 지금은 jvm 이다 // given -> 가짜 데이터 UserRequest.JoinDTO reqDTO = new UserRequest.JoinDTO(); reqDTO.setEmail("haha@nate.com"); reqDTO.setPassword("1234"); reqDTO.setUsername("haha"); String requestBody = om.writeValueAsString(reqDTO); // System.out.println(requestBody); // {"username":"haha","password":"1234","email":"haha@nate.com"} // when -> 테스트 실행 ResultActions actions = mvc.perform( // 주소가 틀리면 터지고, json 아닌거 넣으면 터지고, 타입이 달라도 터지고. 따라서 미리 터진다고 알려줌 MockMvcRequestBuilders .post("/join") .content(requestBody) .contentType(MediaType.APPLICATION_JSON) ); // eye -> 결과 눈으로 검증 String responseBody = actions.andReturn().getResponse().getContentAsString(); //System.out.println(responseBody); // {"status":200,"msg":"성공","body":{"id":4,"username":"haha","email":"haha@nate.com","createdAt":"2025-05-13 11:45:23.604577"}} // then -> 결과를 코드로 검증 // json의 최상위 객체를 $ 표기한다 actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.id").value(4)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.username").value("haha")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.email").value("haha@nate.com")); } @Test public void login_test() throws Exception { // given UserRequest.LoginDTO reqDTO = new UserRequest.LoginDTO(); reqDTO.setUsername("ssar"); reqDTO.setPassword("1234"); String requestBody = om.writeValueAsString(reqDTO); // System.out.println(requestBody); // when ResultActions actions = mvc.perform( MockMvcRequestBuilders .post("/login") .content(requestBody) .contentType(MediaType.APPLICATION_JSON) ); // eye String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody); // then (jwt 길이만 검증) -> 길이 변환 가능. 패턴만 확인 actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.accessToken", matchesPattern("^[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+$"))); } @Test public void update_test() throws Exception { // given UserRequest.UpdateDTO reqDTO = new UserRequest.UpdateDTO(); reqDTO.setEmail("ssar@gmail.com"); reqDTO.setPassword("1234"); String requestBody = om.writeValueAsString(reqDTO); // System.out.println(requestBody); // when ResultActions actions = mvc.perform( MockMvcRequestBuilders .put("/s/api/user") .content(requestBody) .contentType(MediaType.APPLICATION_JSON) .header("Authorization", "Bearer " + accessToken) ); // eye String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody); // then actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.id").value(1)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.username").value("ssar")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.email").value("ssar@gmail.com")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.createdAt", matchesPattern("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d+"))); } @Test public void checkUsernameAvailable_test() throws Exception { // given String username = "ssar"; // when ResultActions actions = mvc.perform( MockMvcRequestBuilders .get("/api/check-username-available/{username}", username) ); // eye String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody); // then actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.available").value(false)); } }

2. BoardController 테스트

package shop.mtcoding.blog.integre; import com.fasterxml.jackson.databind.ObjectMapper; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import shop.mtcoding.blog._core.util.JwtUtil; import shop.mtcoding.blog.board.BoardRequest; import shop.mtcoding.blog.user.User; import static org.hamcrest.Matchers.nullValue; @AutoConfigureMockMvc // MockMvc 클래스가 IOC로드 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) public class BoardControllerTest { @Autowired private ObjectMapper om; @Autowired private MockMvc mvc; private String accessToken; @BeforeEach // Each = 함수마다 실행됨 | ALL = 한 번만 실행 (static으로 만들어야함 왠만하면 사용 X) public void setUp() { // 테스트 시작 전에 실행 할 코드 System.out.println("setUp"); User ssar = User.builder().id(1).username("ssar").build(); accessToken = JwtUtil.create(ssar); } @AfterEach public void tearDown() { // 테스트 후 정리 할 코드 System.out.println("tearDown"); } // 나머지 메서드도 전부 테스트 // 배열은 0번지만 검사 @Test public void update_test() throws Exception { // given (가짜데이터) Integer boardId = 1; BoardRequest.UpdateDTO reqDTO = new BoardRequest.UpdateDTO(); reqDTO.setTitle("제21"); reqDTO.setContent("내21"); reqDTO.setIsPublic(true); String requestBody = om.writeValueAsString(reqDTO); // JAVA 오브젝트를 JSON 오브젝트로 바꿈 System.out.println(requestBody); // when (테스트 실행) ResultActions actions = mvc.perform( // Exception으로 미리 알려주는 에러를 잡음 MockMvcRequestBuilders .put("/s/api/board/" + boardId) .content(requestBody) .contentType(MediaType.APPLICATION_JSON) .header("Authorization", "Bearer " + accessToken) ); // eye (결과 눈으로 검증) String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody); // then (결과 코드로 검증) actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.id").value(1)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.title").value("제21")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.content").value("내21")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.isPublic").value("true")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.userID").value("1")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.createdAt") .value(Matchers.matchesPattern("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{6}$"))); } @Test public void BoardOne_test() throws Exception { // given (가짜데이터) Integer boardId = 1; User user = User.builder().id(1).username("ssar").build(); // when (테스트 실행) ResultActions actions = mvc.perform( // Exception으로 미리 알려주는 에러를 잡음 MockMvcRequestBuilders .get("/api/board/" + boardId) .sessionAttr("sessionUser", user) ); // eye (결과 눈으로 검증) String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody); // then (결과 코드로 검증) actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.id").value(1)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.title").value("제목1")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.content").value("내용1")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.isPublic").value("true")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.userID").value("1")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.createdAt") .value(Matchers.matchesPattern("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{6}$"))); } @Test public void detail_test() throws Exception { // given (가짜데이터) String boardId = "4"; // when (테스트 실행) ResultActions actions = mvc.perform( // Exception으로 미리 알려주는 에러를 잡음 MockMvcRequestBuilders .get("/api/board/" + boardId + "/detail") ); // eye (결과 눈으로 검증) String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody); // then (결과 코드로 검증) actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.id").value(4)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.title").value("제목4")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.content").value("내용4")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.isPublic").value(true)); // true는 boolean 타입이어야 합니다 actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.isOwner").value(false)); // false는 boolean 타입이어야 합니다 actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.isLove").value(false)); // false는 boolean 타입이어야 합니다 actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.loveCount").value(2)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.username").value("love")); // createdAt 검증 actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.createdAt") .value(Matchers.matchesPattern("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}[+-]\\d{2}:\\d{2}$"))); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.loveId").value(nullValue())); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.replies[0].id").value(3)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.replies[0].content").value("댓글3")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.replies[0].username").value("ssar")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.replies[0].isOwner").value(false)); } @Test public void list_test() throws Exception { // given (가짜데이터) String page = "1"; String keyword = "제목1"; // when (테스트 실행) ResultActions actions = mvc.perform( MockMvcRequestBuilders .get("/") .param("keyword", keyword) .param("page", page) ); // eye (결과 눈으로 검증) String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody); // then (결과 코드로 검증) actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.boards[0].id").value(16)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.boards[0].title").value("제목16")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.boards[0].content").value("내용16")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.boards[0].isPublic").value(true)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.boards[0].userId").value(1)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.boards[0].createdAt") .value(Matchers.matchesPattern("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d+$"))); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.prev").value(0)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.next").value(2)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.current").value(1)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.size").value(3)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.totalCount").value(11)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.totalPage").value(4)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.isFirst").value(false)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.isLast").value(false)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.keyword").value("제목1")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.numbers", Matchers.hasSize(4))); } @Test public void save_test() throws Exception { // given (가짜데이터) BoardRequest.SaveDTO reqDTO = new BoardRequest.SaveDTO(); reqDTO.setTitle("제목21"); reqDTO.setContent("내용21"); reqDTO.setIsPublic(true); String requestBody = om.writeValueAsString(reqDTO); // JAVA 오브젝트를 JSON 오브젝트로 바꿈 // System.out.println(requestBody); // when (테스트 실행) ResultActions actions = mvc.perform( // Exception으로 미리 알려주는 에러를 잡음 MockMvcRequestBuilders .post("/s/api/board") .content(requestBody) .contentType(MediaType.APPLICATION_JSON) .header("Authorization", "Bearer " + accessToken) ); // eye (결과 눈으로 검증) String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody); // then (결과 코드로 검증) actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.id").value(21)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.title").value("제목21")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.content").value("내용21")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.isPublic").value("true")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.userID").value("1")); } }

3. ReplyController 테스트

package shop.mtcoding.blog.integre; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.transaction.Transactional; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import shop.mtcoding.blog._core.util.JwtUtil; import shop.mtcoding.blog.reply.ReplyRequest; import shop.mtcoding.blog.user.User; import static org.hamcrest.Matchers.matchesPattern; import static org.hamcrest.Matchers.nullValue; @Transactional @AutoConfigureMockMvc @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) public class ReplyControllerTest { @Autowired private ObjectMapper om; @Autowired private MockMvc mvc; private String accessToken; @BeforeEach public void setUp() { // 테스트 시작 전에 실행할 코드 System.out.println("setUp"); User ssar = User.builder() .id(1) .username("ssar") .build(); accessToken = JwtUtil.create(ssar); } @AfterEach public void tearDown() { // 끝나고 나서 마무리 함수 // 테스트 후 정리할 코드 System.out.println("tearDown"); } @Test public void save_test() throws Exception { // given ReplyRequest.SaveDTO reqDTO = new ReplyRequest.SaveDTO(); reqDTO.setBoardId(1); reqDTO.setContent("댓글6"); String requestBody = om.writeValueAsString(reqDTO); System.out.println(requestBody); // when ResultActions actions = mvc.perform( MockMvcRequestBuilders .post("/s/api/reply") .content(requestBody) .contentType(MediaType.APPLICATION_JSON) .header("Authorization", "Bearer " + accessToken) ); // eye String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody); // then actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.id").value(6)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.content").value("댓글6")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.userId").value(1)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.boardId").value(1)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.createdAt", matchesPattern("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d+"))); } @Test public void delete_test() throws Exception { // given Integer id = 1; // when ResultActions actions = mvc.perform( MockMvcRequestBuilders .delete("/s/api/reply/{id}", id) .header("Authorization", "Bearer " + accessToken) ); // eye String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody); // then actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body").value(nullValue())); } }

4.LoveController 테스트

package shop.mtcoding.blog.integre; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.transaction.Transactional; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import shop.mtcoding.blog._core.util.JwtUtil; import shop.mtcoding.blog.love.LoveRequest; import shop.mtcoding.blog.user.User; @Transactional @AutoConfigureMockMvc @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) public class LoveControllerTest { @Autowired private ObjectMapper om; @Autowired private MockMvc mvc; private String accessToken; @BeforeEach public void setUp() { // 테스트 시작 전에 실행할 코드 System.out.println("setUp"); User ssar = User.builder() .id(1) .username("ssar") .build(); accessToken = JwtUtil.create(ssar); } @AfterEach public void tearDown() { // 끝나고 나서 마무리 함수 // 테스트 후 정리할 코드 System.out.println("tearDown"); } @Test public void saveLove_test() throws Exception { // given LoveRequest.SaveDTO reqDTO = new LoveRequest.SaveDTO(); reqDTO.setBoardId(3); String requestBody = om.writeValueAsString(reqDTO); // System.out.println(requestBody); // when ResultActions actions = mvc.perform( MockMvcRequestBuilders .post("/s/api/love") .content(requestBody) .contentType(MediaType.APPLICATION_JSON) .header("Authorization", "Bearer " + accessToken) ); // eye String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody); // then actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.loveId").value(4)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.loveCount").value(1)); } @Test public void deleteLove_test() throws Exception { // given Integer id = 1; // when ResultActions actions = mvc.perform( MockMvcRequestBuilders .delete("/s/api/love/{id}", id) .header("Authorization", "Bearer " + accessToken) ); // eye String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody); // then actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.loveCount").value(0)); } }
Share article

YunSeolAn