Java SpringBoot의 테스트에 대해 알아보자

2026. 4. 26. 16:52·Java

스프링 프로젝트를 진행하던 중 테스트 방법에 대해 고민하게 되었다.

처음에는 단위 테스트(Mockito), 통합 테스트 등 모든 테스트를 작성하고자 생각했었다.

구현했을 당시에는 문제가 없었지만 개발 초기 단계다 보니 비지니스 로직이 바뀌는 경우가 잦아지면서 테스트 또한 매번 변경해줘야하는 문제가 발생하여 테스트 구조 변경을 고려하게 되었다. 

겸사겸사 블로그에 자바 스프링에서의 테스트에 대해 한번 정리하고자한다.

 

 

테스트에는 크게 슬라이스 테스트, 단위 테스트, 통합 테스트로 나뉜다.

슬라이스 테스트

컨트롤러 레이어, 영속성 레이어, 외부 API 호출 등 특정 레이어만 잘라서 해당 레이어에 필요한 빈들만 올려 테스트

스프링에서는 컨트롤러 레이어를 테스트하는 @WebMvcTest, 영속성 레이어를 테스트하는 @DataJpaTest, 외부 API 호출을 테스트하는 @RestClientTest을 지원하고있다.

단위 테스트

외부 의존성을 가짜 객체(Mock)으로 대체하고 특정 클래스의 비지니스 로직에 집중하여 검증한다.

Given절에서 Mock 객체의 행동을 정의할때, 실제 로직과 괴리가 발생하지 않도록 주의해야한다.

자바 진영에서는 대표적인 Mocking 테스트 프레임워크로 Mockito를 지원하고 있다.

통합 테스트

@SpringBootTest를 사용하여 애플리케이션의 전체 컨텍스트를 로드한다.

DB부터 서비스, 컨트롤러까지 모든 빈이 실제로 연결되어 동작한다.

실제 운영 환경에서 발생할 수 있는 문제를 찾아낼 수 있다.

단점으로 컨텍스트를 로드하는 오버헤드가 발생해 실행 속도가 느리다.

 

1. Mocking (단위 테스트)

개념: 외부 의존성(Service, Repository 등)을 가짜 객체(Mock)로 대체하고, 특정 클래스의 비즈니스 로직만 고립시켜 테스트하는 기법이다.

  • 장점: 실행 속도가 매우 빠르고, 내부 로직의 세세한 예외 케이스를 테스트하기 좋다.
  • 구현 도구: Mockito, @ExtendWith(MockitoExtension.class), @Mock, @InjectMocks

✅ 구현 예시 (Mockito)

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository; // 가짜 객체 생성

    @InjectMocks
    private UserService userService; // 가짜 객체를 주입받는 대상

    @Test
    void 유저_조회_테스트() {
        // given: Mock 객체의 행동 정의 (Stubbing)
        User user = new User(1L, "Jay");
        when(userRepository.findById(1L)).thenReturn(Optional.of(user));

        // when: 실제 로직 실행
        User result = userService.getUser(1L);

        // then: 결과 검증 및 메서드 호출 여부 확인
        assertThat(result.getName()).isEqualTo("Jay");
        verify(userRepository, times(1)).findById(1L);
    }
}

 

Mocking은 내부 동작을 정의해줘야해서 메서드 호출, 반환값 등 비지니스 로직과 강하게 결합된다. 즉, 비지니스 로직에 변화가 생길 경우 테스트의 Given(주어진 것)과 Then(검증)에 변동이 생긴다.

 

2. @SpringBootTest (통합 테스트)

개념: 실제 애플리케이션과 유사하게 Spring Context를 로드하여, 빈(Bean) 간의 상호작용과 DB 연동까지 한꺼번에 검증하는 기법이다.

  • 장점: 설정 오류, DB 제약 조건, 빈 주입 문제 등 실제 운영 환경에서 발생할 문제를 사전에 잡을 수 있다.
  • 구현 도구: @SpringBootTest, @AutoConfigureMockMvc, TestRestTemplate

✅ 구현 예시 (@SpringBootTest)

@SpringBootTest
@AutoConfigureMockMvc
class UserIntegrationTest {

    @Autowired
    private MockMvc mockMvc; // 실제 컨테이너에 뜬 컨트롤러에 요청을 보냄

    @Autowired
    private UserRepository userRepository;

    @Test
    void 유저_회원가입_통합테스트() throws Exception {
        // given: 실제 DB 환경 준비
        String userJson = "{\"name\":\"Jay\", \"email\":\"jay@example.com\"}";

        // when & then: 실제 HTTP 요청을 보내고 응답 및 DB 상태 확인
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(userJson))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("Jay"));

        // 실제 데이터가 저장되었는지 DB 조회 검증
        assertThat(userRepository.findAll()).hasSize(1);
    }
}

 

3. @WebMvcTest (표현 계층 테스트)

개념: 컨트롤러(Controller) 레이어에 집중하여 테스트하는 기법이다. 전체 빈을 올리지 않고 웹 관련 컴포넌트(Security, Filter, Interceptor 등)만 로드하며, 하위 서비스 레이어는 Mock으로 대체하여 테스트 한다.

  • 장점: HTTP 요청 파라미터 검증, 응답 메시지 형식(JSON), 상태 코드 등 웹 계층의 책임을 빠르게 검증할 수 있다.
  • 구현 도구: @WebMvcTest, MockMvc, @MockBean

✅ 구현 예시

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc; // HTTP 요청을 시뮬레이션하는 객체

    @MockBean
    private UserService userService; // 가짜 서비스 주입

    @Test
    void 유저_상세_조회_API_테스트() throws Exception {
        // given
        given(userService.getUser(1L)).willReturn(new User(1L, "Jay"));

        // when & then
        mockMvc.perform(get("/api/users/1"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("Jay"));
    }
}

 

4. @DataJpaTest (영속성 계층 테스트)

개념: JPA 관련 설정들만 로드하여 데이터베이스 접근 로직을 테스트한다. 실제 DB를 사용하여 쿼리가 정상적으로 동작하는지, 엔티티 매핑이 올바른지 확인한다.

  • 장점: 실제 DB와 상호작용하므로 SQL 문법 오류나 제약 조건 위반을 잡을 수 있으며, 테스트가 끝나면 자동으로 롤백되어 DB 상태가 유지된다.
  • 구현 도구: @DataJpaTest, TestEntityManager, UserRepository

✅ 구현 예시

@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void 유저_저장_후_이메일로_찾기_테스트() {
        // given
        User user = new User("Jay", "jay@example.com");
        userRepository.save(user);

        // when
        Optional<User> found = userRepository.findByEmail("jay@example.com");

        // then
        assertThat(found).isPresent();
        assertThat(found.get().getName()).isEqualTo("Jay");
    }
}

 

참고로 JPA를 사용하는 RDB 뿐만 아니라 NoSQL의 경우도 @DataMongoTest와 같이 영속성 계층을 테스트하기 위한 어노테이션이 지원된다. 

 

5. @RestClientTest (외부 API 호출 테스트)

개념: 서비스가 외부 API(타 시스템)를 호출할 때 사용하는 RestTemplate이나 WebClient 등의 동작을 테스트한다. 실제로 외부 서버를 띄우지 않고 가짜 응답을 보내는 서버(MockServer)를 활용한다.

  • 장점: 외부 서비스의 응답 시나리오를 설정하여 클라이언트 로직이 잘 대응하는지 확인할 수 있습니다.
  • 구현 도구: @RestClientTest, MockRestServiceServer

✅ 구현 예시

@RestClientTest(ExternalApiService.class)
class ExternalApiServiceTest {

    @Autowired
    private ExternalApiService apiService; // 테스트 대상

    @Autowired
    private MockRestServiceServer server; // 가짜 외부 서버

    @Test
    void 외부_날씨_API_호출_테스트() {
        // given: 외부 서버가 응답할 가짜 JSON 정의
        server.expect(requestTo("https://api.weather.com/today"))
              .andRespond(withSuccess("{\"status\":\"SUNNY\"}", MediaType.APPLICATION_JSON));

        // when
        String result = apiService.getTodayWeather();

        // then
        assertThat(result).isEqualTo("SUNNY");
    }
}

 

 

 

최종적으로 내가 선택한 방식은 단위 테스트 없이 통합 테스트 및 슬라이스 테스트를 통해 높은 테스트 커버리지를 유지하고, 결합도를 낮췄다.

단위 테스트에 비해 통합 테스트의 경우 어떤 값을 넣었을 때 어떤 결과가 나오는지에만 집중하고, 결합도가 낮고, 테스트의 Then에만 변동이 생기기 때문이다. 무엇보다 통합 테스트와 단위 테스트에서 중복으로 검증하는 메서드들이 발생하면서 혼자 프로젝트를 하는 상황에서 비효율적이라는 생각이 들어서였다.

이번 기회에 테스트에 대해 좀 더 고민해볼 수 있었던 것 같다. 앞으로 테스트를 짤 때 다양한 선택지 중 현재 상황을 고려할 수 있는 계기가 되었으면 한다.

'Java' 카테고리의 다른 글

모던 자바 인 액션 - chapter 7, 8 병렬 데이터 처리와 성능, 컬렉션 API 개선  (0) 2024.11.30
모던 자바 인 액션 - chapter 6 스트림 데이터 수집  (1) 2024.11.29
모던 자바 인 액션 - chapter 4, 5 스트림과 활용  (1) 2024.11.20
모던 자바 인 액션 - chapter 2, 3 동작 파라미터화, 람다 표현식  (1) 2024.11.19
모던 자바 인 액션 - chapter 1 자바 8 무슨 일이 일어나고 있는가  (0) 2024.11.19
'Java' 카테고리의 다른 글
  • 모던 자바 인 액션 - chapter 7, 8 병렬 데이터 처리와 성능, 컬렉션 API 개선
  • 모던 자바 인 액션 - chapter 6 스트림 데이터 수집
  • 모던 자바 인 액션 - chapter 4, 5 스트림과 활용
  • 모던 자바 인 액션 - chapter 2, 3 동작 파라미터화, 람다 표현식
뽀루피
뽀루피
  • 뽀루피
    뽀루피의 개발스토리
    뽀루피
  • 전체
    오늘
    어제
    • 분류 전체보기 (83) N
      • 프로젝트 (3)
      • Java (32) N
        • JPA (4) N
        • Spring (8)
      • FrontEnd (4)
        • JavaScript (1)
        • Flutter (3)
      • DB (4)
        • SQL (4)
      • 알고리즘 (10)
      • Git (3)
      • SSAFY (0)
      • Figma (1)
      • Notion (0)
      • Jira (0)
      • 기획 (0)
      • 네트워크 (10)
      • 우아한테크코스 (2)
      • Infra (5)
        • AWS (0)
        • Docker (3)
      • AI (1)
      • 자격증 (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    redis streams
    알고리즘
    네트워크
    백준
    크래프트맨 멘탈리티
    네트워크 기초
    웹소켓
    Docker
    모던 자바 인 액션
    n8n
    java
    SQL
    Stomp
    cloudflare
    자바
    Jenkins
    스프링
    트랜잭션
    fetch join
    JPA
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
뽀루피
Java SpringBoot의 테스트에 대해 알아보자
상단으로

티스토리툴바