[SpringBoot] UnitTest

연유라떼·2025년 8월 24일

UnitTest란?

코드의 가장 작은 기능적 단위를 테스트하는 프로세스로, 메서드를 테스트하기 위한 또다른 메서드다.

과거에는 노동 집약적인 테스트를 진행

  • 자동화 된 테스트가 어려운 코드들이 많았음(ex. SQL 중심의 코드)
  • 1회성 개발이 많았기 때문에(개발 후 철수), 코드의 품질보다는 기능적인 완성도만 체크
  • 서버에서 로직과 화면까지 모두 만들어서 보내는 구조가 많았기 때문에 테스트 자동화라고 해도 실제 화면에서 직접 마우스/ 키보드가 움직이는 것처럼 하는 테스트가 많았음

현재의 테스트

  • 자동화된 테스트
  • 빠른 테스트
  • 여러가지 테스트와 방법론 등 테스트의 발전
  • 테스트가 중요하다는 것은 이미 너무 많은 사람들이 알고 있음

테스트가 왜 좋아?

  1. 테스트를 짜면서 나의 코드를 자연스럽게 셀프 코드 리뷰 하게 됨
  2. 테스트하기 어려운 경우 코드가 잘못된 것을 빠르게 알 수 있음
    ex. 너무 다양한 일을 하고 역할이 많은 경우
  3. 테스트가 잘되어있으면 마음껏 리팩토링이 가능
    • 코드의 품질 좋아짐
    • 현재 코드의 아주 세부적인 정책들이 모두 테스트로 문서화가 됨 => RestDocs
    • 다음에 기능을 추가하거나 변경할 때도 더 편하게 기존 긴으의 영향도를 알 수 있고 안심하고 개발이 가능

=> 결론!
장기적으로 볼 때 더 빠르고 안정적인 개발이 가능하게됨

테스트를 잘 하려면?

  • 클래스나 메서드가 SRP를 지키고, 너무 크지 않아야한다
  • 유닛 테스트의 경우 적절한 Mocking으로 격리성을 확보한다
  • 테스트 커버리지를 높여 테스트가 안되는 부분이 없도록 한다



JUnit

JUnit이란?

  • XUnit이라는 유닛테스트 프레임워크의 일환으로 Java용으로 개발된 프레임워크이다.
  • JUnit은 단위테스트를 실행하고 결과를 검증해서 전체 결과를 리포트해주는 프레임워크
  • 사용자가 직접 동작시킬 수도 있으며 Gradle이나 Maven을 통해 빌드하면서 테스트 가능
  • 기본적으로 포함되어있다

Autowired

@Autowired는 스프링 프레임워크에서 사용되는 어노테이션으로, 스프링 컨테이너가 관리하는 빈(Bean) 중에서 필요한 객체의 타입과 일치하는 객체를 찾아 자동으로 주입(DI)해주는 역할을 해준다
즉, 개발자가 직접 객체를 생성하고 연결할 필요 없이, Autowired가 붙은 변수나 생성자에 자동으로 객체를 연결해 줌으로써 코드 작성을 간소화하고 의존성을 관리할 수 있게 하는데,
이는 테스트에서 주입하는 데에 도움을 준다.

우리가 직접 테스트를 해보려면

TestService testService = new TestService(new TestRepository());

이런식으로 반복을 해주어야하는데,
이에 대하여 귀찮으니까 생긴 것을 Autowired라고 생각해주면 된다

그래서 간단하게 독립적인 것들 작성할 때에는
@Autowired를 통해 작성해주면 끝!

@Autowired
private TestService testService;

이러면 TestService 안에 있는 TestRepository 등 다양한 의존성에 대하여 알아서 주입을 해준다.


하지만 작업을 하다보면 의존성 주입이 굉장히 잦아지고
테스트의 반복작업도 어려워지게 된다.

모두가 다 연결되어 동작하다보면 서비스 하나에 대해서만 테스트하려는 것이 힘들어질 수 있다.
가령 User 클래스가 데이터베이스나 외부 API와 같은 복잡한 시스템에 의존하고 있다면, User와 관련된 다른 클래스를 테스트하기 위해 User.class 환경을 통째로 구축해야 하는 어려움이 있다.

이러한 문제를 해결하기 위해 가짜 객체(Mock Object)를 수동으로 만들기도 했는데, 이는 코드의 양을 늘리고 유지보수를 어렵게 만드는 반복적인 작업(boilerplate)으로 생겨난 것이 Mockito 이다!


Mockito

  • 이름과 비슷하게 Mock을 만들어주는 라이브러리
  • 해결하고자 하는 점?
    • 테스트하고자 하는 클래스가 의존하는 클래스를 모두 만들려다보니 테스트 만들기가 어렵다는 번거로움을 해결
    • 모든 클래스가 동작하는 흐름이다보면 테스트 실패에서 어떤 부분이 문제인지 알기가 어려움

-> 가짜(Mock)을 만들어서 내가 원하는 방식으로 동작하게 하자!
= Mockito 라이브러리를 활용

Mockito 예제

예시를 통해 확인해보자

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
	
    @Mock
    private UserRepository userRepository;
    
    @InjectMocks
    private UserService userService;
}

UserRepository에 대하여 가짜 객체인 Mock을 생성하고, userService에 대하여 가짜로 생성한 Mock을 주입할 수 있도록 설계한다

이 때, Mock은 실제 동작이 아닌 가짜 객체이므로, 실제 userRepository를 사용하는 것이 아니므로 인메모리 객체 생성으로 작업을 해주는 것이 좋다.

Mockito 기반 테스트는 일반적으로 given → when → then 구조를 따른다.

  • given : 인메모리 객체를 생성하여 테스트를 위한 환경(상황)을 준비한다.
  • when : 테스트 대상 메서드를 호출하거나, 필요한 Mock 동작을 설정한다.
  • then : 실제 결과와 예측한 결과를 비교·검증(assert)한다.
    이 패턴에 맞추어 테스트 코드를 작성하면 가독성과 일관성이 높아진다.

예제를 확인해보자

@Test
void 로그인_테스트() {
	// given
    User user = new User("test", 20, "test@test.com");
    
    // when
    when(userService.createAccount()).returnThen(user);
    
    // then
    assertThat()....
}

Autowired와 Mockito 차이

목적별 선택 기준

  • 비즈니스 로직 “단위”만 검증: Mockito (@Mock, @InjectMocks)
  • 레이어(웹/JPA) 슬라이스 검증: Spring 슬라이스 테스트 (@WebMvcTest, @DataJpaTest) + 필요 시 @MockBean
  • 실제 환경 통합 검증: @SpringBootTest + 실제 빈 @Autowired (필요하면 일부 의존성만 @MockBean)

사용 방법

  • 공통: JUnit5는 @ExtendWith(SpringExtension.class)암묵 포함(슬라이스/부트 테스트 사용 시).

  • 단위 테스트(순수 Mockito): @ExtendWith(MockitoExtension.class) 또는 MockitoAnnotations.openMocks(this)

  • 슬라이스/통합: @WebMvcTest, @DataJpaTest, @SpringBootTest

  • Mock 주입 위치

    • 순수 Mockito: @Mock + @InjectMocks
    • 스프링 컨텍스트 내: @MockBean(빈을 대체), @SpyBean(부분 모킹)

서비스 단위 테스트 (DB/외부호출 제거)

  • 목표: 서비스 메서드의 분기/계산/예외 처리만 보기

  • 선택: Mockito 단독 (스프링 컨텍스트 없음)

    class OrderServiceTest {
      @InjectMocks OrderService service;
      @Mock OrderRepository repo;
    
      @BeforeEach void init(){ MockitoAnnotations.openMocks(this); }
    
      @Test void 할인적용() {
        when(repo.findById(1L)).thenReturn(Optional.of(order(...)));
        var price = service.calculatePrice(1L);
        assertEquals(9000, price);
      }
    }

JPA 레이어 테스트 (엔티티/쿼리/리포지토리)

  • 목표: 엔티티 매핑, 쿼리 메서드, 영속성 컨텍스트 동작

  • 선택: @DataJpaTest (내장 DB/H2 + 스캔 최소화)

    • 필요 시 Testcontainers로 실제 DB 붙여 신뢰성 ↑

간단한 정리

  1. DB/외부호출 없이 순수 로직만? → Mockito
  2. HTTP 요청/응답, 바인딩/예외/필터? → @WebMvcTest + @MockBean
  3. 엔티티/리포지토리/쿼리 검증? → @DataJpaTest
  4. 실제 빈 조합·설정·시큐리티·트랜잭션까지? → @SpringBootTest (+ 필요 시 일부만 @MockBean)

일반적인 사용 조합

  • 서비스 로직: Mockito 단위 테스트 + 경계 조건/예외 케이스 빡세게
  • 컨트롤러: @WebMvcTest + MockMvc + 전역 예외/밸리데이션 검증
  • JPA: @DataJpaTest + Testcontainers(MySQL/Postgres)
  • 통합: @SpringBootTest(webEnvironment = RANDOM_PORT) + RestAssured/MockMvc

다음 글에서는 통합 테스트와 WebMvcTest에 대하여 심화적으로 가져와보겠다!

profile
일단 공부해보겠습니다..

0개의 댓글