어제 오늘 내일

[Spring Boot] "테스트 없는 배포는 도박이다!" JUnit5 & Mockito 단위 테스트 입문 본문

IT/SpringBoot

[Spring Boot] "테스트 없는 배포는 도박이다!" JUnit5 & Mockito 단위 테스트 입문

hi.anna 2026. 3. 14. 07:51

 

"바빠서 테스트 짤 시간이 없어요."라는 핑계, 많이 들어보셨죠? 하지만 테스트 코드가 없는 코드는 '레거시(Legacy)'일 뿐입니다. 테스트가 없으면 리팩토링도, 기능 추가도 무서워서 못 하게 되니까요.

오늘은 스프링 부트 테스트의 양대 산맥, JUnit5(실행기)Mockito(가짜 객체)를 사용해 서비스 계층(Service Layer)을 테스트하는 방법을 알아보겠습니다.

 


 

1. 단위 테스트(Unit Test)란?

"딱 하나(Unit)만 격리해서 테스트한다"는 뜻입니다.

우리가 MemberService를 테스트하고 싶은데, 이 서비스가 MemberRepository를 통해 진짜 DB에 접근한다면?

  • 테스트가 느려집니다. (DB 통신)
  • 데이터가 꼬입니다. (테스트할 때마다 DB 초기화 필요)
  • 결론: 서비스 로직만 검증하고 싶은데, DB 때문에 방해를 받습니다.

그래서 우리는 진짜 DB 대신 가짜(Mock) 객체를 사용하여, 오직 서비스 로직에만 집중합니다. 이것이 바로 Mocking입니다.

 


 

2. 필수 친구들 소개

  1. JUnit5: 자바용 단위 테스트 프레임워크입니다. (@Test 등을 제공)
  2. Mockito: 가짜 객체(Mock)를 쉽고 간편하게 만들어주는 라이브러리입니다.
  3. AssertJ: 테스트 결과를 검증(assertThat)하는 라이브러리입니다. (JUnit 기본 assertEquals보다 훨씬 가독성이 좋습니다!)

 


 

3. 실전! 회원가입 테스트 코드 작성하기

자, MemberServicejoin() 메서드를 테스트해 봅시다.

① 테스트 클래스 세팅

@ExtendWith(MockitoExtension.class) // 1. Mockito를 쓰겠다고 선언
class MemberServiceTest {

    @Mock // 2. 가짜 리포지토리 생성 (껍데기만 있음)
    private MemberRepository memberRepository;

    @InjectMocks // 3. 가짜 리포지토리를 이 서비스에 주입!
    private MemberService memberService;

    // ... 테스트 메서드 작성
}

② BDD 스타일 (Given - When - Then)

테스트 코드는 무조건 이 3단계를 따르는 것이 국룰(Rule)입니다.

  1. Given (준비): 이런 상황이 주어졌을 때
  2. When (실행): 이 코드를 실행하면
  3. Then (검증): 이런 결과가 나와야 한다
@Test
@DisplayName("회원가입 성공 테스트")
void join() {
    // given (준비)
    Member member = new Member("userA", "password123");

    // 가짜 리포지토리에게 "save()가 호출되면, 무조건 member를 리턴해!"라고 교육(Stubbing)시킴
    // (실제 DB에 저장 안 함)
    given(memberRepository.save(any())).willReturn(member);

    // when (실행)
    Long savedId = memberService.join(member);

    // then (검증)
    // 리턴된 ID가 null이 아니어야 한다.
    assertThat(savedId).isNotNull();

    // save() 메서드가 딱 1번 호출되었는지 검사 (행위 검증)
    verify(memberRepository, times(1)).save(member);
}

 

 


 

4. 실패 케이스도 테스트해야죠! (예외 발생)

성공하는 테스트보다 더 중요한 게 실패하는 테스트입니다.
"중복 회원이 가입하면 예외가 터져야 한다"는 시나리오를 짜볼까요?

@Test
@DisplayName("중복 회원 예외 발생")
void join_duplicate() {
    // given
    Member member1 = new Member("userA", "1234");
    Member member2 = new Member("userA", "5678"); // 이름 같음!

    // "이름으로 찾았을 때 이미 회원이 있다고 해!" (가짜 행동 정의)
    given(memberRepository.findByName("userA"))
            .willReturn(Optional.of(member1));

    // when & then (실행과 검증을 동시에)
    // memberService.join(member2)를 실행했을 때, IllegalStateException이 터져야 한다!
    assertThatThrownBy(() -> memberService.join(member2))
            .isInstanceOf(IllegalStateException.class)
            .hasMessage("이미 존재하는 회원입니다.");
}

 

 


 

5. Mockito 핵심 요약 (given vs when)

Mockito를 쓸 때 가장 헷갈리는 부분입니다.

  • Stubbing (가짜 행동 정의):
  • given(mock.method()).willReturn(value): BDD 스타일 (추천!)
  • when(mock.method()).thenReturn(value): 기본 스타일
  • Verification (행위 검증):
  • verify(mock).method(): 이 메서드가 호출되었니?

 


 

마치며

오늘의 결론입니다.

  1. 단위 테스트는 외부 의존성(DB, Network)을 끊고 내 로직만 검증하는 것이다.
  2. Mockito를 사용해 가짜(Mock) 객체를 주입(@InjectMocks)받아 테스트한다.
  3. Given-When-Then 패턴으로 작성하여 가독성을 높인다.
  4. 실패 케이스(예외)를 반드시 꼼꼼하게 테스트한다.

이렇게 단위 테스트를 작성해 두면, 나중에 코드를 대대적으로 수정해도 "테스트 돌려보니 초록불(Pass)이네? 안심하고 배포하자!" 할 수 있는 자신감이 생깁니다.

다음 포스팅에서는 "그래도 진짜 DB랑 잘 붙는지 확인해야지!" 통합 테스트(@SpringBootTest)와 H2 데이터베이스 활용법에 대해 알아보겠습니다.

도움이 되셨다면 좋아요와 댓글 부탁드립니다! 😊

 

 

반응형
Comments