스프링부트

방 관련 단위 테스트 해보기

masxer 2025. 8. 3. 15:05

테스트의 목적은 기능이 잘 작동함을 보여주는 것이 아니라, 문제점을 발견하고 개선하는 데 있다.

테스트를 할 때 자주 쓰는 구조

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