@SpringBootTest가 3초 걸리는 이유 — Slice 테스트와 Testcontainers
@SpringBootTest
class UserServiceTest {
@Autowired UserService service; // 전체 컨텍스트 시작
}
이 테스트는 실행될 때마다 3초가 걸린다 — 전체 Spring 컨텍스트를 시작하기 때문이다. 빈을 전부 로드하고, 자동 구성을 평가하고, 내장 서버까지 준비한다. 단순히 서비스 로직 하나를 테스트하는 데 이것은 너무 무겁다. Spring Boot는 @WebMvcTest, @DataJpaTest 등 Slice 테스트로 컨텍스트를 최소화한다.
이 글은 Spring Boot Test의 계층(전체 vs Slice), Mock 사용, Testcontainers 통합, JUnit 6 전환을 풀어간다. (Spring Boot Reference - Testing)
테스트 계층 — 무엇을 얼마나 띄울 것인가
flowchart TD
UNIT["단위 테스트<br/>(순수 Java, Spring 없음)"] --> SLICE["Slice 테스트<br/>(특정 계층만)"]
SLICE --> INT["통합 테스트<br/>(전체 컨텍스트)"]
INT --> E2E["End-to-End<br/>(실제 환경)"]
| 테스트 유형 | 어노테이션 | 컨텍스트 | 속도 | 용도 |
|---|---|---|---|---|
| 단위 | (Spring 없음) | 없음 | 빠름 (<100ms) | 순수 로직 |
| Slice (Web) | @WebMvcTest |
MVC 계층만 | 빠름 ( ~ 1s) | 컨트롤러 테스트 |
| Slice (JPA) | @DataJpaTest |
JPA 계층만 | 빠름 ( ~ 1s) | 리포지토리 테스트 |
| 통합 | @SpringBootTest |
전체 | 느림 ( ~ 3s) | 전체 흐름 |
@WebMvcTest — 컨트롤러 Slice 테스트
// Spring Boot 4 / Java 25 — @WebMvcTest
import org.springframework.boot.test.autoconfigure.web.servlet.*;
import org.springframework.test.web.servlet.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired MockMvc mockMvc;
@MockBean UserService service; // 서비스는 mock으로 대체
@Test
void getUser_returnsJson() throws Exception {
when(service.findById(1L))
.thenReturn(new UserResponse(1L, "Alice", "alice@test.com", 30, null));
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Alice"))
.andExpect(jsonPath("$.email").value("alice@test.com"));
}
}
@WebMvcTest는DispatcherServlet,HttpMessageConverter,Validator만 로드한다 —UserService,UserRepository는 로드하지 않는다. 의존성은@MockBean으로 주입한다. (Spring Boot Reference - @WebMvcTest)
왜 전체 컨텍스트를 띄우지 않는가? @SpringBootTest가 3초 걸리는 이유는 — 수백 개의 bean을 생성하고, 자동 구성을 평가하고, DB 연결을 설정하기 때문이다. 하지만 컨트롤러의 "올바른 JSON을 반환하는가?"를 테스트하려면 컨트롤러 + JSON 직렬화 + 검증 로직만 있으면 된다. 서비스 계층은 실제 DB를 호출하지 않아도 — mock이 "Alice"를 반환하도록 설정하면, 컨트롤러가 그것을 올바른 JSON으로 변환하는지만 검증할 수 있다.
비유: 자동차의 브레이크를 점검할 때 엔진, 변속기, 에어백까지 모두 켤 필요가 없다. 브레이크 페달과 브레이크 시스템만 작동시키면 된다. @WebMvcTest는 "브레이크만 점검"하는 모드이고, @SpringBootTest는 "전체 차량 시동"이다.
@DataJpaTest — JPA Slice 테스트
// Spring Boot 4 / Java 25 — @DataJpaTest
import org.springframework.boot.test.autoconfigure.orm.jpa.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
@DataJpaTest
class UserRepositoryTest {
@Autowired UserRepository repository;
@Autowired TestEntityManager em;
@Test
void findByEmail_returnsUser() {
em.persist(new User("Alice", "alice@test.com", 30));
em.flush();
User found = repository.findByEmail("alice@test.com").orElseThrow();
assertThat(found.getName()).isEqualTo("Alice");
}
}
@DataJpaTest는 인메모리 H2 DB를 사용하고, 각 테스트 후 롤백한다 (@Transactional포함). 외부 DB가 필요 없다. (Spring Boot Reference - @DataJpaTest)
Testcontainers — 실제 DB로 통합 테스트
@DataJpaTest의 H2는 실제 운영 DB(PostgreSQL, MySQL)와 다를 수 있다. Testcontainers로 실제 DB 컨테이너를 띄워 테스트한다.
// Spring Boot 4 / Java 25 — Testcontainers
import org.springframework.boot.testcontainers.service.connection.*;
import org.testcontainers.containers.PostgreSQLContainer;
@DataJpaTest
@Testcontainers
class UserRepositoryIntegrationTest {
@Container
@ServiceConnection // Spring Boot 3.1+ 자동 연결
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:17");
@Autowired UserRepository repository;
@Test
void customPostgresFunction() {
// PostgreSQL 특정 기능(JSONB, array 등)을 실제 DB로 테스트
em.persist(new User("Alice", "alice@test.com", 30));
assertThat(repository.existsByEmail("alice@test.com")).isTrue();
}
}
@ServiceConnection(Spring Boot 3.1+)은 컨테이너의 연결 정보를 자동으로 Spring DataSource에 주입한다.@DynamicPropertySource를 수동으로 쓸 필요가 없다. (Spring Boot Reference - Testcontainers)
@MockBean — 의존성 교체
// Spring Boot 4 / Java 25 — @MockBean
@SpringBootTest
class OrderServiceTest {
@Autowired OrderService orderService;
@MockBean PaymentClient paymentClient; // 실제 결제 API 대신 mock
@Test
void orderSucceeds_whenPaymentSucceeds() {
when(paymentClient.charge(anyString(), anyLong()))
.thenReturn(new PaymentResult("tx-123", "SUCCESS"));
OrderResult result = orderService.placeOrder("order-1", 50000);
assertThat(result.status()).isEqualTo("COMPLETED");
verify(paymentClient).charge("order-1", 50000);
}
}
JUnit 6 — Spring Boot 4.0의 테스트 프레임워크
Spring Boot 4.0은 JUnit 6를 사용한다. (Spring Framework 7 - JUnit 6)
| 항목 | JUnit 5 (Spring Boot 3) | JUnit 6 (Spring Boot 4) |
|---|---|---|
| 패키지 | org.junit.jupiter.api |
동일 (하위 호환) |
@Test |
org.junit.jupiter.api.Test |
동일 |
| AssertJ | 3.x | 3.27+ |
| Mockito | 5.x | 5.x+ |
JUnit 5에서 6으로의 전환은 대부분의 애플리케이션 코드에 영향을 주지 않는다 — Jupiter API(
@Test,@BeforeEach등)는 그대로 유지된다. 내부 아키텍처 개선이 주요 변화다.
@SpringBootTest 전략 — 통합 테스트 최적화
// Spring Boot 4 / Java 25 — 통합 테스트 최적화
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class FullStackTest {
@Autowired TestRestTemplate restTemplate;
@Test
void createUser_endToEnd() {
ResponseEntity<UserResponse> response = restTemplate.postForEntity(
"/api/users",
new CreateUserRequest("Alice", "alice@test.com", 30),
UserResponse.class
);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(response.getBody().name()).isEqualTo("Alice");
}
}
통합 테스트는 느리지만 — 가장 높은 신뢰도를 제공한다. 전체 스택(HTTP → 컨트롤러 → 서비스 → DB → 응답)이 정상 동작하는지 검증한다. 테스트 피라미드 원칙: 단위 테스트가 많고, 통합 테스트는 핵심 흐름만.
실습 — 테스트 전략 비교
// Spring Boot 4 / Java 25 — TestStrategyDemo.java
// Spring 없이 테스트 레이어 시뮬레이션
import java.util.*;
public class TestStrategyDemo {
interface UserRepo { Optional<String> findNameById(Long id); }
// 단위 테스트: mock으로 의존성 대체
static void unitTest() {
UserRepo mockRepo = id -> Optional.of("Alice");
var name = mockRepo.findNameById(1L).orElseThrow();
assert name.equals("Alice") : "단위 테스트 통과";
System.out.println("단위 테스트: PASS (의존성 mock)");
}
// 통합 테스트: 실제 의존성 사용
static void integrationTest() {
UserRepo realRepo = id -> Optional.of("Alice"); // 실제 DB 호출 시뮬레이션
var name = realRepo.findNameById(1L).orElseThrow();
assert name.equals("Alice") : "통합 테스트 통과";
System.out.println("통합 테스트: PASS (실제 의존성)");
}
public static void main(String[] args) {
unitTest();
integrationTest();
System.out.println("\n단위 테스트: 빠름, 의존성 격리");
System.out.println("통합 테스트: 느림, 전체 검증");
}
}
AssertJ — 가독성 높은 검증
// Spring Boot 4 / Java 25 — AssertJ (Spring Boot 기본)
import static org.assertj.core.api.Assertions.*;
@Test
void userCreation_validatesFields() {
User user = new User("Alice", "alice@test.com", 30);
assertThat(user)
.isNotNull()
.extracting(User::getName, User::getEmail, User::getAge)
.containsExactly("Alice", "alice@test.com", 30);
assertThat(user.getName())
.startsWith("A")
.hasSize(5)
.isNotBlank();
}
AssertJ는 JUnit의
assertEquals보다 가독성이 훨씬 높다 — 메서드 체이닝으로 자연어에 가까운 검증을 작성한다. Spring Boot Starter Test에 기본 포함되어 있다.
@TestConfiguration — 테스트 전용 bean
// Spring Boot 4 / Java 25 — 테스트 전용 bean 등록
@TestConfiguration
public class TestConfig {
@Bean
public PaymentClient mockPaymentClient() {
return new MockPaymentClient(); // 실제 API 대신 항상 성공하는 mock
}
}
@SpringBootTest
@Import(TestConfig.class) // 테스트 설정 로드
class OrderServiceTest {
@Autowired PaymentClient client; // MockPaymentClient 주입
}
요약 — 이 글의 결론
- Slice 테스트(
@WebMvcTest,@DataJpaTest)로 컨텍스트를 최소화한다. 전체 애플리케이션을 띄우지 않고 특정 계층만 테스트하여 속도를 높인다. @MockBean으로 의존성을 mock으로 교체한다. 외부 API, 느린 서비스를 실제로 호출하지 않고 테스트한다.- Testcontainers +
@ServiceConnection으로 실제 DB를 테스트한다. H2가 아닌 PostgreSQL/MySQL 컨테이너를 띄워 DB 특정 기능을 검증한다. - JUnit 6 전환은 애플리케이션 코드에 큰 영향이 없다. Jupiter API(
@Test,@BeforeEach)가 동일하다. @SpringBootTest는 느리지만 가장 높은 신뢰도를 제공한다. 핵심 비즈니스 흐름에만 사용한다.
생각해 볼 문제
@MockBean과@SpyBean의 차이는? 각각 언제 써야 하는가?@DataJpaTest에서 H2 대신 Testcontainers PostgreSQL을 사용하려면 어떻게 하는가?@WebMvcTest가@Servicebean을 로드하지 않는 이유는?@MockBean으로 어떻게 해결하는가?- Spring Boot 4.0에서 JUnit 6으로 전환 시 기존 JUnit 4 테스트는 어떻게 되는가?
- Testcontainers를 CI 환경(GitHub Actions 등)에서 사용할 때 주의점은?
@ServiceConnection(Spring Boot 3.1+)이 기존@DynamicPropertySource를 어떻게 대체하는가?
참고
- Spring Boot 4.0 - Testing - 접근 2026-07-10
- Spring Boot 4.0 - Testcontainers - 접근 2026-07-10
- Spring Boot 4.0 - @WebMvcTest - 접근 2026-07-10
- JUnit 6 - 접근 2026-07-10
- AssertJ Documentation - 접근 2026-07-10
- Mockito Documentation - 접근 2026-07-10
- Spring Boot 4.0과 이전 버전(Spring Boot 3.x)의 가장 큰 차이점은 무엇인가? 마이그레이션 시 주의할 점은?
- 이 글에서 다룬 개념을 실제 프로젝트에 적용할 때 가장 먼저 고려해야 할 것은?
- Testcontainers Documentation - 접근 2026-07-10
- Spring Boot Test Utils - 접근 2026-07-10
- Spring Boot 4.0 Reference - 접근 2026-07-10
'Develop Artifacts > Spring Boot' 카테고리의 다른 글
| SpringBoot - 08. actuator (0) | 2026.07.12 |
|---|---|
| SpringBoot - 07. security (0) | 2026.07.12 |
| SpringBoot - 05. spring-data-jpa (0) | 2026.07.12 |
| SpringBoot - 4. rest api (0) | 2026.07.12 |
| SpringBoot - 03. embedded-server (0) | 2026.07.12 |
