https://letsdodev.tistory.com/211
에서 작업했던 Todo API 에서 단위테스트, 통합테스트를 진행한다.
#1 TEST CODE 작성 : Todo API 만들기
※ 테스트 코드 작성 이전, 테스트 코드 작성에 필요한 Todo API 작성해서 기록한 게시물※ 테스트 코드는 다음 게시물에 작성 예정[프로젝트 생성]- 스프링 이니셜라이저 사용spring-boot : 3.4.0 verja
letsdodev.tistory.com
@WebMvcTest vs @SpringBootTest
- @WebMvcTest:
- 컨트롤러만 로드하고, 그 외의 빈(서비스, 리포지토리 등)은 로드하지 않음
- 빈 로딩 범위: 컨트롤러만 로드, 서비스나 리포지토리와 같은 다른 빈은 자동 로드되지 않음
- 주로 웹 계층 테스트에 사용
- 서비스나 리포지토리 등을 Mock 객체로 주입
- 예시: @WebMvcTest(TodoController.class) (컨트롤러만 테스트)
- @SpringBootTest:
- 전체 애플리케이션 컨텍스트를 로드하므로, 실제 서비스와 리포지토리 빈까지 포함
- 빈 로딩 범위: 전체 애플리케이션 컨텍스트를 로드 -> 서비스, 리포지토리, 컨트롤러 등이 모두 실제로 로드
- 실제 클래스를 사용하여 테스트할 수 있음
- 전체 애플리케이션을 테스트할 때 사용
- 예시: @SpringBootTest (애플리케이션 전체 테스트)
TestRestTemplate VS RestTemplte
목적 | 실제 HTTP 요청을 보낼 때 사용 | 테스트 환경에서 HTTP 요청을 시뮬레이션 |
사용 용도 | 실제 애플리케이션에서 외부 API 호출 | Spring Boot 테스트에서 REST API를 테스트 |
기능 | HTTP 요청과 응답을 처리, 다양한 설정 지원 | 테스트 환경에서 요청을 보내고 응답을 검증 |
설정 | RestTemplate을 직접 설정해야 함 | @SpringBootTest 등에서 자동 설정 |
디버깅 및 편의성 | 일반적인 HTTP 클라이언트 기능 제공 | 테스트 중 디버깅과 설정이 더 간편함 |
[테스트 전 세팅]
1. 임의 값 INSERT

2. DB 체크

[단위테스트 VS 통합테스트]
단위 테스트 : 가짜 서비스 객체 빈 사용 -> 즉, 컨트롤러 계층까지만 테스트 가능하다는 얘기
통합 테스트 : 실제 서비스 객체 빈 사용 -> 즉, 전체 로직 테스트 가능
[#1 단위테스트 : TodoControllerMockTest.java + 결과]
- TodoControllerMockTest.java
package com.testcode.jpah2;
import com.testcode.jpah2.todo.controller.TodoController;
import com.testcode.jpah2.todo.dto.ResponseTodoDto;
import com.testcode.jpah2.todo.service.TodoService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
// static import
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.junit.jupiter.api.Assertions.*;
// 단위 테스트 : 가짜 서비스 객체 빈 사용 -> 즉, 컨트롤러 계층까지만 테스트 가능하다는 얘기
@ExtendWith(SpringExtension.class) // junit 5 일 경우 vs junut 4 일 경우 @RunWith(SpringRunner.class)
@WebMvcTest(value = TodoController.class)
public class TodoControllerMockTest {
// log
private static final Logger log = LoggerFactory.getLogger(TodoControllerMockTest.class); // 테스트 유형 : 단위 테스트
@Autowired
private MockMvc mvc;
@MockBean
private TodoService todoService;
@Test
public void findAllTodoTest() throws Exception {
// GIVEN
List<ResponseTodoDto> mockResponse = new ArrayList<ResponseTodoDto>();
// CASE 1
// 예상 결과 (JSON 형식의 문자열 대비 객체)
String mockDateStr = "2025-01-01 22:46:40.138806";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS");
ResponseTodoDto mockResponseTodo = new ResponseTodoDto(1L,
"첫 번째 todo 입력",
LocalDateTime.parse(mockDateStr, formatter),
false);
mockResponse.add(mockResponseTodo);
// CASE 2
// String mockResponseTodo = "첫 번째 todo 입력"; // 예상 결과 (이미 DB 에 저장된 값)
// mockResponse.add(mockResponseTodo);
String expected = "[{" +
"\"id\": 1," +
"\"title\": \"첫 번째 todo 입력\"," +
"\"date\": \"2025-01-01T22:46:40.138806\"," +
"\"isFinish\": false" +
"}]"; // 예상 결과 (JSON 형식의 문자열)
// WHEN
// 가짜 빈 todoService 정의
// test 클래스에서 controller mapping 되는 api 요청을 보낼 때 해당 controller 메소드에서
// 사용되는 service 의 메소드를 여기서 정의하고, 정의한 서비스 로직이 수행되게 한다.
// 쉽게 예시 들자면 service 메소드 인터셉트 해서 재정의 후 재정의한 메소드 실행시키는 것.
/*
// 타겟 서비스 메소드
@GetMapping("/findAll")
public List<ResponseTodoDto> findAllTodo() {
return todoService.findAllTodo();
}
*/
when(todoService.findAllTodo()).thenReturn(mockResponse); // thenReturn 안에 데이터 타입은 실제 TodoService.findAllTodo() 의 반환 데이터 타입과 동일 해야함
// THEN
MvcResult result = mvc
.perform(MockMvcRequestBuilders.request(HttpMethod.GET, "/api/todo/findAll")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andReturn();
// log.info(expected);
// log.info(result.getResponse().getContentAsString());
// CASE 1
JSONAssert.assertEquals(expected, result.getResponse().getContentAsString(), false);
// CASE 2
// assertEquals(expected, result.getResponse().getContentAsString());
}
// 조희 - 실패할
@Test
public void findAllTodoTestFailed() throws Exception {
// GIVEN
List<ResponseTodoDto> mockResponse = new ArrayList<ResponseTodoDto>();
// CASE 1
// 예상 결과 (JSON 형식의 문자열 대비 객체)
String mockDateStr = "2025-01-01 22:46:40.138806";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS");
ResponseTodoDto mockResponseTodo = new ResponseTodoDto(1L,
"첫 번째 todo 입력",
LocalDateTime.parse(mockDateStr, formatter),
false);
mockResponse.add(mockResponseTodo);
// CASE 2
// String mockResponseTodo = "첫 번째 todo 입력"; // 예상 결과 (이미 DB 에 저장된 값)
// mockResponse.add(mockResponseTodo);
String expected = "[{" +
"\"id\": 1," +
"\"title\": \"존재하지 않은 실패할 todo\"," +
"\"date\": \"2025-01-01T22:46:40.138806\"," +
"\"isFinish\": false" +
"}]"; // 예상 결과 (JSON 형식의 문자열)
// WHEN
// 가짜 빈 todoService 정의
// test 클래스에서 controller mapping 되는 api 요청을 보낼 때 해당 controller 메소드에서
// 사용되는 service 의 메소드를 여기서 정의하고, 정의한 서비스 로직이 수행되게 한다.
// 쉽게 예시 들자면 service 메소드 인터셉트 해서 재정의 후 재정의한 메소드 실행시키는 것.
/*
// 타겟 서비스 메소드
@GetMapping("/findAll")
public List<ResponseTodoDto> findAllTodo() {
return todoService.findAllTodo();
}
*/
when(todoService.findAllTodo()).thenReturn(mockResponse); // thenReturn 안에 데이터 타입은 실제 TodoService.findAllTodo() 의 반환 데이터 타입과 동일 해야함
// THEN
MvcResult result = mvc
.perform(MockMvcRequestBuilders.request(HttpMethod.GET, "/api/todo/findAll")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andReturn();
// log.info(expected);
// log.info(result.getResponse().getContentAsString());
// CASE 1
JSONAssert.assertEquals(expected, result.getResponse().getContentAsString(), false);
// CASE 2
// assertEquals(expected, result.getResponse().getContentAsString());
}
}
- 결과



[#2 통합테스트 : TodoControllerSpringBootTest.java + 결과]
- TodoControllerSpringBootTest.java
package com.testcode.jpah2;
//import com.testcode.jpah2.todo.repository.TodoRepository;
//import com.testcode.jpah2.todo.service.TodoService;
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.http.ResponseEntity;
import org.springframework.test.context.junit.jupiter.SpringExtension;
// static import
import static org.assertj.core.api.Assertions.*;
// 통합 테스트 : 실제 서비스 객체 빈 사용 -> 즉, 전체 로직 테스트 가능
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = Jpah2Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TodoControllerSpringBootTest {
@Autowired
private TestRestTemplate testRestTemplate;
// @SpringBootTest 어노테이션으로 controller, service, repository 모두 빈으로 등록된 상태
// 현재 테스트에서는 service, repository 를 대상으로 하는 부분 테스트가 아니라
// api 요청 프로세스 전체 테스트이기 때문에 아래 빈 주입을 하지 않고
// **restTemplate 으로 api 요청하여 테스트 하였다.
// 만약, service 나 repository 에 대한 부분 테스트를 진행하려면
// 아래 주석 처럼 빈 주입을 해주어야 객체의 메소드를 사용할 수 있다.
// @Autowired
// private TodoService todoService;
// @Autowired
// private TodoRepository todoRepository;
// 조희 - 성공할
@Test
public void findAllTodoTest() throws Exception {
// GIVEN
/*
// CASE 1
String expected = "[{" +
"\"id\": 1," +
"\"title\": \"첫 번째 todo 입력"," +
// "\"date\": \"2025-01-01 22:46:40.138806\"," +
"\"isFinish\": false" +
"}]"; // 예상 결과 (JSON 형식의 문자열)
*/
// CASE 2
String expected = "첫 번째 todo 입력"; // 예상 결과 (이미 DB 에 저장된 값)
// WHEN 은 따로 없음 : service 클래스를 MockBean 이 아닌 실제 클래스로 사용할 것이기 떄문
// THEN
ResponseEntity<String> response = testRestTemplate.getForEntity("/api/todo/findAll", String.class);
// CASE 1
// JSONAssert.assertEquals(expected, result.getResponse().getContentAsString(), false);
// CASE 2
assertThat(response.getBody()).contains(expected);
}
// 조희 - 실패할
@Test
public void findAllTodoTestFailed() throws Exception {
// GIVEN
String expected = "존재하지 않은 실패할 todo"; // 예상 결과 (이미 DB 에 저장된 값)
// WHEN 은 따로 없음 : service 클래스를 MockBean 이 아닌 실제 클래스로 사용할 것이기 떄문
// THEN
ResponseEntity<String> response = testRestTemplate.getForEntity("/api/todo/findAll", String.class);
// CASE 1
// JSONAssert.assertEquals(expected, result.getResponse().getContentAsString(), false);
// CASE 2
assertThat(response.getBody()).contains(expected);
}
}
- 결과



해당 게시물에서 HttpMethod 타입 중 GET 방식만 다뤘다.
POST, PUT, PATCH 타입도 추후 블로그 포스팅 하겠다.
'API' 카테고리의 다른 글
스프링 스케줄러와 배치 #1 - Spring Scheduler(@Scheduled), TaskScheduler (0) | 2025.03.09 |
---|---|
Spring AOP 설명(Annotaion, Class) 및 예제 (1) | 2025.01.17 |
#1 TEST CODE 작성 : Todo API 만들기 (1) | 2024.12.14 |
[spring webSocket - 스프링 웹소켓] : 간이 채팅방 구현 (4) | 2024.09.16 |
WebSocketHandler - 오버라이드 메소드, override method 정리 (0) | 2024.09.16 |