Spring Boot 테스트 코드 작성

한눈에 보기

Spring Boot 테스트는 컨테이너를 어디까지 띄우느냐에 따라 통합 테스트와 슬라이스 테스트로 나뉜다. @SpringBootTest, @WebMvcTest, @DataJpaTest 활용법과 컨텍스트 캐싱 주의점을 다룬다.

최종 업데이트 2026.09.17

Spring Boot 테스트 코드는 스프링 컨테이너를 어디까지 띄우느냐에 따라 통합 테스트와 슬라이스(Slice) 테스트로 나뉜다. @SpringBootTest는 전체 애플리케이션 컨텍스트(ApplicationContext)를 로딩해 실제 운영 환경에 가까운 검증을 제공하고, @WebMvcTest나 @DataJpaTest 같은 슬라이스 애너테이션(Annotation)은 특정 계층에 필요한 빈(Bean)만 로딩해 빠른 피드백을 제공한다. 좋은 테스트 전략은 이 둘과 순수 단위 테스트를 목적에 맞게 조합하는 것이다.

어디까지 띄울 것인가

테스트의 신뢰도와 속도는 반비례하는 경향이 있다. 컨테이너 없이 Mockito로 의존성을 대역(Mock) 처리하는 단위 테스트가 가장 빠르고, 전체 컨텍스트에 실제 데이터베이스까지 붙이는 통합 테스트가 가장 느리지만 신뢰도가 높다. 따라서 도메인 로직은 스프링 없이 순수 단위 테스트로, 각 계층의 설정과 매핑은 슬라이스 테스트로, 전체 흐름은 소수의 통합 테스트로 검증하는 피라미드 구조가 권장된다. Spring Boot 3.x는 JUnit 5를 기본으로 하며, 테스트 클래스에 별도 확장 선언 없이 부트 테스트 애너테이션만 붙이면 스프링 확장이 함께 적용된다.

주요 테스트 애너테이션

애너테이션로딩 범위주 용도
@SpringBootTest전체 컨텍스트여러 계층을 관통하는 통합 테스트
@WebMvcTest웹 계층(컨트롤러, 필터, 컨버터)요청 매핑, 검증, 직렬화 테스트
@DataJpaTestJPA 관련 빈과 리포지토리엔티티 매핑, 쿼리 메서드 테스트
@JsonTestJSON 직렬화 컴포넌트ObjectMapper 매핑 검증
@RestClientTestHTTP 클라이언트 빈외부 API 호출 코드 테스트

슬라이스 테스트는 해당 계층 밖의 빈을 로딩하지 않으므로, 컨트롤러가 의존하는 서비스처럼 빠진 빈은 대역으로 채워야 한다. Spring Boot 3.4부터는 @MockBean 대신 @MockitoBean이 표준 명칭이며, 역할은 같다.

컨트롤러 테스트: @WebMvcTest와 MockMvc

웹 계층 테스트는 서버를 실제로 띄우지 않고 MockMvc로 디스패처 서블릿(DispatcherServlet)에 가짜 요청을 흘려 보내는 방식으로 작성한다.

@WebMvcTest(MemberController.class)
class MemberControllerTest {

    @Autowired
    MockMvc mockMvc;

    @MockitoBean
    MemberService memberService;

    @Test
    void 회원_단건_조회에_성공한다() throws Exception {
        given(memberService.findById(1L))
                .willReturn(new MemberResponse(1L, "김개발"));

        mockMvc.perform(get("/api/members/1"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("김개발"));
    }
}

이 방식은 요청 매핑, 파라미터 바인딩, 예외 처리기, JSON 직렬화까지 웹 계층의 책임을 실제 스프링 MVC 구성으로 검증하면서도 서비스 로직과는 분리된다는 장점이 있다.

영속성 계층 테스트: @DataJpaTest

@DataJpaTest는 기본적으로 내장 메모리 데이터베이스로 교체해 실행하고, 각 테스트를 트랜잭션으로 감싼 뒤 끝나면 롤백해 테스트 간 격리를 보장한다.

@DataJpaTest
class MemberRepositoryTest {

    @Autowired
    MemberRepository memberRepository;

    @Autowired
    TestEntityManager em;

    @Test
    void 이름으로_회원을_조회한다() {
        em.persist(new Member("김개발"));
        em.flush();
        em.clear();

        List<Member> result = memberRepository.findByName("김개발");

        assertThat(result).hasSize(1);
    }
}

flush와 clear로 영속성 컨텍스트를 비워야 1차 캐시가 아닌 실제 쿼리 실행을 검증할 수 있다는 점에 주의한다. 운영 데이터베이스와 같은 환경에서 검증하고 싶다면 @AutoConfigureTestDatabase(replace = Replace.NONE)와 Testcontainers를 조합해 실제 MySQL이나 PostgreSQL 컨테이너로 테스트한다. 방언(Dialect) 차이로 내장 데이터베이스에서만 통과하는 쿼리를 걸러낼 수 있기 때문이다.

컨텍스트 캐싱과 주의할 점

스프링 테스트 프레임워크는 같은 설정의 컨텍스트를 한 번만 띄우고 테스트 클래스 간에 재사용하는 컨텍스트 캐싱을 제공한다. 그런데 @MockitoBean 조합, @ActiveProfiles, properties 속성이 다르면 별도의 캐시 키가 되어 컨텍스트가 새로 뜬다. 테스트 클래스마다 설정이 제각각이면 빌드 시간에서 컨텍스트 기동이 큰 비중을 차지하게 되므로, 공통 설정을 추상 클래스나 메타 애너테이션으로 통일하는 것이 좋다. @DirtiesContext는 컨텍스트를 오염시켰을 때 캐시를 버리게 하는 장치이지만 남용하면 캐싱 이점이 사라진다.

실제 서버 구동이 필요한 경우에는 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)로 임의 포트에 서버를 띄우고 TestRestTemplate이나 WebTestClient로 호출한다. 고정 포트는 병렬 빌드에서 충돌하므로 피한다.

면접에서 이렇게 물어본다

  • 단위 테스트, 슬라이스 테스트, 통합 테스트를 어떤 기준으로 나누어 작성하는지 설명해 보라.
  • @WebMvcTest가 @SpringBootTest와 비교해 무엇을 로딩하고 무엇을 로딩하지 않는지 말해 보라.
  • @DataJpaTest에서 flush와 clear를 호출하는 이유를 영속성 컨텍스트와 연결해 설명해 보라.
  • 스프링 테스트의 컨텍스트 캐싱 동작과 캐시가 재사용되지 못하는 조건을 말해 보라.
  • 내장 메모리 데이터베이스 대신 Testcontainers를 쓰는 이유를 설명해 보라.
Spring / JPA 문서 전체 보기

이 개념, 내 코드에서는 어떻게 물어볼까요

데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.

공고 넣고 시작하기