테스트의 목적은 기능이 잘 작동함을 보여주는 것이 아니라, 문제점을 발견하고 개선하는 데 있다.
테스트를 할 때 자주 쓰는 구조
1. 단위 테스트 (Service만 테스트)
@SpringBootTest
class RoomServiceTest {
@Autowired
private RoomService roomService;
@Test
void 방_생성_정상작동() {
RoomRequest request = RoomRequest.builder()
.roomName("테스트 방")
.roomDescription("설명")
.maxParticipants(5)
...
.build();
RoomResponse response = roomService.createRoom(request);
assertNotNull(response.getRoomId());
assertEquals("테스트 방", response.getRoomName());
}
}
2. 컨트롤러 + API 테스트 (`MockMvc`)
@WebMvcTest(RoomController.class)
class RoomControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private RoomService roomService;
@Test
void 방_생성_API_테스트() throws Exception {
RoomRequest request = new RoomRequest(...);
RoomResponse response = new RoomResponse(...);
when(roomService.createRoom(any())).thenReturn(response);
mockMvc.perform(post("/api/v1/companion-rooms")
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.roomName").value("테스트 방"));
}
}
3. 통합 테스트 (DB까지 확인)
@SpringBootTest
@Transactional
class RoomIntegrationTest {
@Autowired
private RoomRepository roomRepository;
@Test
void 방_정상_저장_확인() {
Room room = Room.builder()
.roomName("통합 테스트 방")
.roomMaxCount(4)
.roomCurrentCount(1)
.build();
Room saved = roomRepository.save(room);
assertTrue(roomRepository.findById(saved.getRoomId()).isPresent());
}
}
왜 꼭 테스트를 작성해야 할까?
| 이유 | 설명 |
| 코드가 의도대로 동작하는지 검증 | 예상치 못한 버그 방지 |
| 리팩토링 시 안전성 확보 | 테스트 통과 확인하면서 구조 개선 가능 |
| 협업 시 신뢰성 보장 | 다른 사람 코드가 나의 로직을 깨지 않는지 확인 가능 |
| CI/CD 자동화 가능 | GitHub Action 등으로 배포 전 자동 검사 가능 |
단위 테스트 하기
단위 테스트는 주로 서비스 구현체에 구현한 로직에 대해 테스트 코드를 작성한다.
- 먼저 방 생성에 대한 테스트 코드를 작성해보겠다.
- 패턴 : given - when -then
createRoom() : 방 생성
[Given]
- RoomRequest 객체를 하나 만든다. (유효한 값으로)
- 이 객체에 들어갈 `roomName`, `roomDescription`, `schedule`, `maxParticipants` 등의 값을 설정한다.
[When]
- `roomService.createRoom()`을 호출한다.
- 그 결과로 `RoomResponse`가 리턴될 것임.
[Then]
- 반환된 RoomResponse의 값이 방금 넣은 값과 일치하는지 검증한다. (`roomName`, `maxParticipants`, 날짜 등)
- DB에 실제로 저장되었는지도 확인하고 싶으면 `getRoomById()`로 조회해서 확인할 수도 있어.
- 혹은 예외가 발생하면 안 되는 경우 `assertDoesNotThrow()` 등으로 확인.
방 단건 조회하기
1. [정상 케이스] 존재하는 roomId로 조회
given
- 미리 저장된 Room이 DB에 존재함 (예: `roomService.createRoom(...)`으로 사전 생성)
when
- `getRoomById(savedRoom.getRoomId())` 호출
then
- 반환된 RoomResponse의 필드(roomName, roomDescription 등)가 저장한 값과 동일함을 검증 (예: `assertThat(response.getRoomName()).isEqualTo(...)`)
2. [예외 케이스] 존재하지 않는 roomId로 조회
given
- DB에 존재하지 않는 ID (예: `99999L`)
when
- `getRoomById(99999L)` 호출
then
- `CustomException` 발생
- 예외의 에러 코드 또는 메시지가 `ROOM_NOT_FOUND`인지 확인
assertThatThrownBy(() -> roomService.getRoomById(99999L))
.isInstanceOf(CustomException.class)
.hasMessageContaining("ROOM_NOT_FOUND");
공통으로 쓸 객체 생성
@BeforeEach
void setUp() {
baseRoomRequest = RoomRequest.builder()
.roomName("기본 방 제목")
.roomDescription("기본 방 설명")
.maxParticipants(5)
.schedule(RoomRequest.ScheduleDto.builder()
.dateRanges(List.of(
RoomRequest.ScheduleDto.DateRangeDto.builder()
.startDate(LocalDateTime.of(2025, 8, 1, 0, 0))
.endDate(LocalDateTime.of(2025, 8, 3, 0, 0))
.build()
))
.rangeCount(1)
.totalDays(3)
.build()
)
.build();
baseRoomRequest2 = RoomRequest.builder()
.roomName("기본 방 제목2")
.roomDescription("기본 방 설명2")
.maxParticipants(3)
.schedule(RoomRequest.ScheduleDto.builder()
.dateRanges(List.of(
RoomRequest.ScheduleDto.DateRangeDto.builder()
.startDate(LocalDateTime.of(2025, 8, 2, 0, 0))
.endDate(LocalDateTime.of(2025, 8, 6, 0, 0))
.build()
))
.rangeCount(1)
.totalDays(3)
.build()
)
.build();
}
방 생성 테스
@Test
@DisplayName("방 생성 테스트 : 정상적인 요청으로 방이 생성되어야 한다")
void createRoom() {
// given
// when
RoomResponse roomResponse = roomService.createRoom(baseRoomRequest);
// then
assertThat(roomResponse).isNotNull();
assertThat(roomResponse.getRoomName()).isEqualTo("기본 방 제목");
assertThat(roomResponse.getRoomDescription()).contains("기본 방 설명");
assertThat(roomResponse.getMaxParticipants()).isEqualTo(5);
}
방 단건 조회 테스트
@Test
@DisplayName("방 단건 조회 테스트 : 존재하는 roomId로 조회")
void getRoomById_success() {
// given
RoomResponse created = roomService.createRoom(baseRoomRequest);
// when
RoomResponse response = roomService.getRoomById(created.getRoomId());
// then
assertThat(response).isNotNull();
assertThat(response.getRoomName()).isEqualTo("기본 방 제목");
assertThat(response.getRoomDescription()).isEqualTo("기본 방 설명");
assertThat(response.getMaxParticipants()).isEqualTo(5);
}
실패하는 경우도 테스트 해본다.
@Test
@DisplayName("방 단건 조회 테스트 : 존재하지 않는 roomId로 조회하면 예외 발생")
void getRoomById_fail() {
// given
Long notExistRoomId = 99999L;
// when & then
assertThatThrownBy(() -> roomService.getRoomById(notExistRoomId))
.isInstanceOf(CustomException.class)
.hasMessageContaining("존재하지 않는 방입니다.");
}
- assertThatThrownBy(() -> roomService.getRoomById(notExistRoomId)) : 여기 있는 람다식을 실행했을 때 예외가 발생해야 한다는 걸 의미
- .isInstanceOf(CustomException.class) : 생한 예외의 타입이 `CustomException`이어야 한다는 뜻 → 다른 예외 (`NullPointerException`, `IllegalArgumentException` 등)가 뜨면 이 테스트는 실패한다.
- .hasMessageContaining("ROOM_NOT_FOUND") : 예외 메시지 안에 "ROOM_NOT_FOUND"라는 문자열이 포함되어 있어야 한다는 조건 → 보통 `new CustomException(ROOM_NOT_FOUND)` 이런 식으로 생성된 예외 객체 내부 메시지를 검증
모든 방 조회
@Test
@DisplayName("방 전체 조회 테스트 : 저장한 방 리스트 확인")
void getAllRooms() {
// given
roomService.createRoom(baseRoomRequest);
roomService.createRoom(baseRoomRequest2);
// when
List<RoomResponse> rooms = roomService.getAllRooms();
// then
assertThat(rooms).isNotNull();
assertThat(rooms.size()).isGreaterThanOrEqualTo(2);
assertThat(rooms).extracting(RoomResponse::getRoomName)
.contains("기본 방 제목", "기본 방 제목2");
}
방 삭제
@Test
@DisplayName("방 삭제 테스트 : soft delete로 isDeleteRoom = true 설정")
void deleteRoomById() {
// given
RoomResponse created = roomService.createRoom(baseRoomRequest);
// when
roomService.deleteRoomById(created.getRoomId());
// then
Room room = roomRepository.findById(created.getRoomId())
.orElseThrow(() -> new RuntimeException("room not found"));
assertThat(room.getIsDeleteRoom()).isTrue();
}'스프링부트' 카테고리의 다른 글
| 방 관련 컨트롤러, 서비스 등 로직 작성 (2) | 2025.08.03 |
|---|---|
| REST API와 RESTful API의 차이점을 알아보자 (3) | 2025.07.23 |
| JPA (2) | 2025.07.23 |
| JDBC와 MyBatis (1) | 2025.07.22 |
| 롬복(Lombok) (1) | 2025.07.15 |