[SPRING] 테스트 코드 - 단위, 슬라이스, 통합 테스트 톺아보기

림민지·2025년 5월 1일

Today I Learn

목록 보기
52/62

👀 Overview

먼저 본격적으로 들어가기 전에, 각 테스트가 언제, 왜 쓰이는지 대애충 보고 가보자

구분단위 테스트 (Unit Test)슬라이스 테스트 (Slice Test)통합 테스트 (Integration Test)
목적특정 클래스/메서드가 제대로 작동하는지애플리케이션의 한 영역만 분리해서 테스트시스템 전체 흐름이 잘 작동하는지
스프링 컨텍스트❌ 필요 없음 (보통 Mockito로 대체)⭕ 관련된 Bean 일부만 로딩⭕ 전체 ApplicationContext 로딩
실제 빈 사용 여부❌ Mock 객체로 대체 (@Mock 등 사용)⭕ 실제 Bean 일부 사용 (@DataJpaTest 등)⭕ 실제 Bean 전부 사용
테스트 속도🟢 매우 빠름🟡 중간🔴 가장 느림
의존성 처리대부분 Mock으로 처리실제 DB (H2 등) 또는 설정된 DB실제 DB 또는 테스트 환경 DB
예시 어노테이션@ExtendWith(MockitoExtension.class)@WebMvcTest, @DataJpaTest@SpringBootTest
테스트 대상하나의 메서드, 클래스Controller만, Repository만 등 일부분전체 시스템 흐름 (Controller → Service → DB)
대표적 사용 도구Mockito, AssertJ 등Spring Boot Test + MockMvc 등Spring Boot Test + 실제 환경 구성
실제 실행 흐름 확인❌ 어렵다🔸 부분적으로 가능✅ 전부 확인 가능

예쁘게 만들어줘서 고마워요 나만의 작은 지선생님


1️⃣ 단위테스트 by MockitoExtension

🧩 단위 테스트란?

: 하나의 클래스 또는 메서드만 단독으로 테스트하는 것
(외부 의존성은 제거하거나 Mock 처리)
→ 시스템을 조각조각 최소 단위로 뜯어가지고 테스트!

하향식 테스트 → Stub
상향식 테스트 → Driver

@ExtendWith(MockitoExtension.class)

단위테스트를 진행하고 싶은 테스트 클래스의 최상단에 넣어주면 되는데,
얘는 스프링한테 "Mockito를 확장해서 단위 테스트에 사용할게!!!"라고 말해주는 아이임

✅ 외부 의존성을 전부 @Mock으로 가짜로 만들어서 테스트
✅ 스프링 컨테이너도 안 띄움

주로 ServiceRepository 같은 로직 중심의 단위 테스트에서 사용

@ExtendWith(MockitoExtension.class) <--여기!!
class TodoServiceTest {

    @Mock <- 짜가 주입
    private TodoRepository todoRepository;

    @InjectMocks <- 테스트할 대상
    private TodoService todoService;

    @Test <- 테스트 시작
    void todo를_조회하면_리스트가_반환된다() {
        // given, when, then
    }
}

좀더 자세한 세부 로직 (@Test 내부) 는 아래의 링크에서 확인 가능하다
https://velog.io/@minjee2758/SPRING-Test-%EC%BD%94%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0


2️⃣ 슬라이스 테스트

위의 @ExtendWith(MockitoExtension.class)@WebMvcTest는 모두 단위 테스트에 가깝지만, 정확히 말하면 테스트 범위와 목적이 다르기 때문에 둘 다 무조건 "단위 테스트"라고 보긴 어렵다!

구분단위 테스트 (Unit Test)슬라이스 테스트 (Slice Test)
범위아주 작은 단위 (클래스 하나 등)애플리케이션의 한 영역 (예: Repository만)
스프링굳이 안 띄워도 됨 (JUnit + Mockito로 충분)스프링 일부 띄움 (예: JPA 관련 빈들만)
예시@ExtendWith(MockitoExtension.class)@DataJpaTest, @WebMvcTest
속도가장 빠름단위 테스트보다 느리지만 전체 통합보단 빠름

🧸 슬라이스 테스트의 대표적 어노테이션들

@WebMvcTest
@WebFluxTest
@DataJpaTest
@JsonTest
@RestClientTest

➡️ 이중에 유우명한 이 두 아이를 중점으로 보면 좋을 것 같다! @WebMvcTest, @DataJpaTest

1. @WebMvcTest

  • 스프링 MVC 레이어만 띄움 (Controller, 필터, 인터셉터 등)
  • 실제 HTTP 요청 흐름을 따라감
  • 시큐리티, 필터까지 자동으로 테스트하며, 수동으로 추가/삭제 가능.
  • @SpringBootTest 어노테이션보다 가볍게 테스트 가능!

👀 주의할 점

다음과 같은 내용만 스캔하도록 하자
@Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, HandlerInterceptor
따라서 의존성이 끊기기 때문에, 예를 들면 서비스와 같은 객체들은 @MockBean을 사용해서 만들어 사용해야 한다.

🫛 @MockBean

: 가짜 서비스나 가짜 레포지토리를 스프링이 사용하는 진짜처럼 등록해주는 어노테이션
→ 테스트할 때는 우리는 DB까지 갈 필요가 없으므로, 기존에 사용되던 스프링 Bean이 아닌 ⭐️Mock Bean을 주입!

잠깐 용어 정리를 하자면,,,,
🗄️ ApplicationContext
: 스프링이 사용하는 공식 창고!
Service, Repository, Controller 같은 걸 이 창고에 넣어두고 꺼내 쓴다
🥜 빈(Bean)
: 이 창고에 들어있는 부품 하나하나가 Bean
(예: TodoService, TodoRepository 등)

그러니까 우리가 @MockBean을 쓰면, 스프링이 "오 이거 테스트용이구나 ㅇㅋ" 하고 가짜 객체를 빈으로 등록(=창고에 넣기) 해준다

이미 진짜 TodoService 같은 게 창고에 있었다고 해도, @MockBean TodoService라고 선언하면
👉 진짜는 빼고, 가짜(Mock)를 대신 넣는다!

@WebMvcTest(TodoController.class)
class TodoControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private TodoService todoService;// 여기서 진짜 서비스 대신 가짜(Mock)로 바꿔치기!

☑️ 알아둬야하는 키워드

mockMvc : 가짜 웹 요청을 테스트할 수 있게 도와주는 도구
.andExpect() : 결과 예상(응답 값 검사)
jsonPath("$.message") : json 응답에서 특정 필드 값 체크!

예제 코드

@WebMvcTest(TodoController.class)
class TodoControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private TodoService todoService; //가짜 서비스 주입

    @Test
    void todo_단건_조회에_성공한다() throws Exception {
        // given
        long todoId = 1L;
        String title = "title";
        AuthUser authUser = new AuthUser(1L, "email", UserRole.USER);
        User user = User.fromAuthUser(authUser);
        UserResponse userResponse = new UserResponse(user.getId(), user.getEmail());
        TodoResponse response = new TodoResponse(
                todoId,
                title,
                "contents",
                "Sunny",
                userResponse,
                LocalDateTime.now(),
                LocalDateTime.now()
        );

        // when
        when(todoService.getTodo(todoId)).thenReturn(response);

        // then
        mockMvc.perform(get("/todos/{todoId}", todoId)) //📦 "/todos/1로 GET 요청을 보낸다고 가정하고, 컨트롤러가 제대로 응답하는지 확인해보쟈!"
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(todoId))
                .andExpect(jsonPath("$.title").value(title));
    }

성공하는 테스트의 예시를 살짝 봐보자!

MockBean으로 투두 서비스를 등록하구, 유저와 response값을 만들어놓고
상황을 when 안에 넣어서 테스트하게하자

마지막으로 mockMvc로 예상하는 값과 비교해서 정말 그 값을 내놓는지 테스트하면 끗~!


2. @DataJpaTest

레포지토리를 테스트할 때 주로 사용된다!

  • Repository 관련된 Bean들만 등록하기 때문에 통합 테스트에 비해서 빠르다
  • Repository에 대한 관심사만 갖기 때문에 테스트 범위가 작다

➡️ 오직 데이터베이스랑 연결해서 데이터 넣고, 찾고, 있는지만 확인해보는 테스트

@DataJpaTest
public class MemberRepositoryTest {
    @Autowired
    private MemberRepository memberRepository;

    @Before
    public void setUp() {
        // 테스트 실행 전에 DB에 가짜 사람 하나 저장!
    }

    @Test
    public void existsByEmail_존재하는경우_true() {
        // 방금 저장한 이메일로 조회했더니 진짜 있대!  (true)
    }

    @Test
    public void existsByEmail_없는경우_false() {
        // 이상한 이메일로 조회했더니 없대! 어머어머 (false)
    }
}
  1. 가짜 DB에 사람 하나 저장

  2. 그 사람이 진짜 저장됐는지 existsByEmail로 확인

  3. 그 이메일이 없으면 false, 있으면 true!

⚙️ @AutoConfigureTestDatabase(replace = NONE) 이 어노테이션을 붙일 수도 있긴한데
기본적으로는 메모리 속 DB(H2) 를 써서 테스트하기 때문에

"나는 진짜 DB(MySQL 등)를 쓰고 싶어!" ~~ 라고 할때 붙이면 된다!

➡️ 테스트 끝나면 데이터는 자동 삭제


3️⃣ 통합 테스트

: 말 그대로 여러 부분을 다 합쳐서 테스트 해버리기
by @SpringBootTest & @AutoConfigureMockMvc

먼저 예시 코드를 보자
Todo를 저장하는데에 성공하는지 실패하는지 알아보는 건데,

@SpringBootTest
@AutoConfigureMockMvc
class TodoIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper; // JSON 직렬화에 사용

    @Test
    void todo_저장_성공() throws Exception {
        Todo todo = new Todo();
        todo.setTitle("할 일 1");
        todo.setCompleted(false);

        mockMvc.perform(post("/todos")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(todo)))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.title").value("할 일 1"))
            .andExpect(jsonPath("$.completed").value(false));
    }

    @Test
    void todo_저장_실패_title없음() throws Exception {
        Todo todo = new Todo();
        todo.setTitle(""); // 빈 값
        todo.setCompleted(false);

        mockMvc.perform(post("/todos")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(todo)))
            .andExpect(status().isBadRequest());
    }
}

여기서 이 private ObjectMapper objectMapper; 이건 사용자가 입력하는 json입력값을 java로 바꿔주는 건데
마지막에 mockMvc로 then 부분 테스트할 때, .content(objectMapper.writeValueAsString(todo)))
입력받은 json을 처리할 수 있게 문자열로 바꿔주는 거당

mockMvc.perform(post("/todos")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(todo)))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.title").value("할 일 1"))
            .andExpect(jsonPath("$.completed").value(false));

이 코드르 하나씩 뜯어보자!

  1. .perform(post("/todos")
    이 url로 테스트 할거다 "/todos" 주소로 POST 요청
    → TodoController에 새로운 할 일을 등록하는 API가 있다고 가정

  2. .contentType(MediaType.APPLICATION_JSON)
    "보낼 데이터는 JSON이야!" 라고 알려주기
    → 서버에게 "나 JSON 보낼게~" 라고 말하는 거

  3. .content(objectMapper.writeValueAsString(todo)))
    todo라는 자바 객체를 JSON 문자열로 바꿔서 HTTP body에 넣기

  1. .andExpect(status().isOk())
    응답 상태 코드가 200 OK인지 확인

  2. .andExpect(jsonPath("$.title").value("할 일 1"))
    응답 JSON에서 "title" 값이 "할 일 1"인지 확인


참고하면 좋을 링크

https://github.com/cheese10yun/spring-guide/blob/master/docs/test-guide.md#%EB%8B%A8%EC%A0%90

profile
@lim_128

0개의 댓글