본문 바로가기
spring

[SpringBoot] JUnit과 Mockito로 테스트 코드 작성법

by 개발자 쿠키 2024. 11. 24.

테스트 코드를 작성하는 이유

코드를 작성이 오래 걸리던 과거와 달리 AI와 다양한 기술의 발전으로 코드 작성시간 역시 급격하게 줄어들었습니다. 이제는 유지보수 가능한 코드를 정확하고 빠르게 작성해야합니다. 여기서 중요한 것은 정확하게입니다. 빠르기만 한다면 누구나 할 수 있고, 정확하기만 한다면 배포날짜를 맞출 수 없습니다. 



SpringBoot Test

SpringBoot 프로젝트를 생성하면 자동으로 추가되는 의존성이 spring-boot-starter-test입니다.
이 의존성에는 JUnit, Mockito, Spring TestContext, AssertJ, Hamcrest 등 테스트에 필요한 라이브러리가 포함되어 있습니다.
따라서, 별도의 설정 없이 테스트 코드를 작성하고 실행할 수 있습니다.

 

Jnuit

자바에서 쓰이는 단위 테스트 프레임워크


단위 테스트 (Unit Test)

더보기

작성한 코드가 의도대로 작동하는지 작은 단위로 검증하는 것
하나의 모듈을 기준으로 독립적으로 진행되는 가장 작은 단위 테스트

 

자주 사용하는 anotation

Junit
- @Test : 해당 메서드를 테스트 메서드로 지정
- @DisplayName : 테스트 이름을 명시
- @BeforeAll : 전체 테스트 시작 전에 한 번만 실행
- @BeforeEach : 각 테스트 케이스 시작 전에 매번 실행
- @AfterEach : 각 테스트 케이스 종료 직전에 실행
- @AfterAll : 전체 테스트 종료 직전에 한 번만 실행

Mockito
- @ExtendWith(MockitoExtension.class) : JUnit 5에서 Mockito를 사용하기 위해 확장
- @Mock : 가짜 객체(mock) 생성
- @InjectMocks : 실제 객체에 Mock 객체 주입

 

테스트 코드

@ExtendWith(MockitoExtension.class)
class BoardServiceTest {
    @InjectMocks
    private BoardService boardService;

    @Mock
    private BoardRepository boardRepository;

    private final BoardRequest mockRequest = new BoardRequest("테스트 제목", "테스트 내용", "테스터");
    private final Board mockBoard = Board.builder()
            .id(1L).title("테스트 제목").content("테스트 내용").author("테스터").build();

    // 1. 게시글 생성
    @Test
    @DisplayName("게시글 생성에 성공한다")
    void 게시글_생성_성공() {
        when(boardRepository.save(any(Board.class))).thenReturn(mockBoard);
        Board createdBoard = boardService.createBoard(mockRequest);
        assertThat(createdBoard.getTitle()).isEqualTo("테스트 제목");
        verify(boardRepository, times(1)).save(any(Board.class));
    }

    // 2. 단일 게시글 조회
    @Test
    @DisplayName("유효한 ID로 게시글 조회에 성공한다")
    void 단일게시글_조회_성공() {
        when(boardRepository.findById(1L)).thenReturn(Optional.of(mockBoard));
        Board foundBoard = boardService.getBoard(1L);
        assertThat(foundBoard.getId()).isEqualTo(1L);
        assertThat(foundBoard.getTitle()).isEqualTo("테스트 제목");
    }

    // 3. 단일 게시글 조회 - 실패 (404)
    @Test
    @DisplayName("존재하지 않는 ID로 조회 시 BOARD_NOT_FOUND 예외가 발생한다")
    void 단일게시글_조회_실패() {
        when(boardRepository.findById(anyLong())).thenReturn(Optional.empty());
        QuoteLineException exception = assertThrows(QuoteLineException.class, () -> boardService.getBoard(999L));
        assertThat(exception.getErrorCode()).isEqualTo(ErrorCode.BOARD_NOT_FOUND);
    }

    // 4. 전체 게시글 조회
    @Test
    @DisplayName("전체 게시글 목록 조회에 성공한다")
    void 전체게시글_조회_성공() {
        List<Board> boards = Arrays.asList(mockBoard, Board.builder().id(2L).title("2번글").build());
        when(boardRepository.findAll()).thenReturn(boards);
        List<Board> foundBoards = boardService.getAllBoards();
        assertThat(foundBoards).hasSize(2);
    }

    // 5. 게시글 수정
    @Test
    @DisplayName("게시글 수정에 성공한다")
    void 게시글_수정_성공() {
        BoardRequest updateRequest = new BoardRequest("수정된 제목", "수정된 내용", "수정자");
        when(boardRepository.findById(1L)).thenReturn(Optional.of(mockBoard));
        when(boardRepository.save(any(Board.class))).thenAnswer(invocation -> invocation.getArgument(0));
        Board updateBoard = boardService.updateBoard(1L, updateRequest);
        assertThat(updateBoard.getTitle()).isEqualTo("수정된 제목");
    }

    // 6. 게시글 삭제
    @Test
    @DisplayName("게시글 삭제에 성공한다")
    void 게시글_삭제_성공() {
        Long idToDelete = 1L;
        boardService.deleteBoard(idToDelete);
        verify(boardRepository, times(1)).deleteById(idToDelete);
    }
}