Code/Test

[더 자바, 애플리케이션을 테스트하는 다양한 방법] 2. Mockito

noahkim_ 2025. 4. 19. 21:26

백기선 님의 인프런 강의 "더 자바, 애플리케이션을 테스트하는 다양한 방법"를 정리한 글입니다.

 

1. 소개

  • Java 계열에서 가장 많이 사용되는 Mock 프레임워크 (50%+)

 

구성 요소 

  • Mock: 실제 객체처럼 동작하지만, 프로그래머가 행동을 제어할 수 있는 객체
  • Mockito: Mock 객체를 쉽게 만들고, 조작하고, 검증할 수 있는 프레임워크

 

대체제

  • EasyMock
  • JMock

 

2. Mockito 시작하기

Gradle

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
    testImplementation 'org.mockito:mockito-core:5.12.0'
    testImplementation 'org.mockito:mockito-junit-jupitor:5.12.0'
}

 

Spring Boot 2.2+

  • spring-boot-starter-test에 자동 포함됨

 

3. 적용

Mock 객체 생성

예제) Mockito.mock()

더보기
MemberService memberService = mock(MemberService.class);
StudyRepository studyRepository = mock(StudyRepository.class);

 

예제) @Mock

더보기
@ExtendWith(MockitoExtension.class)
class StudyServiceTest {
    @Mock MemberService memberService;
    @Mock StudyRepository studyRepository;
}
  • @ExtendWith(MockitoExtension.class)와 함께 사용
  • 필드 또는 메서드 매개변수로 가능

 

예제) 메소드 매개변수 이용

더보기
@ExtendWith(MockitoExtension.class)
class StudyServiceTest {
    @Test
    void createStudyService(@Mock MemberService memberService,
                            @Mock StudyRepository studyRepository) {
        StudyService studyService = new StudyService(memberService, studyRepository);
        assertNotNull(studyService);
    }
}

 

Mock 객체 동작 정의 (Stubbing)

Stubbing API
기능 설명
일반 Stubbing
특정 호출에 대해 값을 리턴하도록 설정
예외 던지기
호출 시 예외를 던지도록 설정
연속 호출 Stubbing
연속 호출 시 각각 다른 값을 리턴하도록 설정
void 메서드 예외 던지기
void 메서드가 호출될 때 예외를 던지도록 설정
void 메서드 실행 없이 무시
void 메서드는 기본적으로 아무 일도 하지 않음
메서드 호출 시 특정 동작 수행
호출 시 사용자 정의 동작 수행 가능
호출된 값에 따라 다르게 동작
특정 인자에 대해 다르게 반환
Argument Matcher 활용
인자 매처 (any(), eq(), 등) 사용 가능

 

예제

더보기
@ExtendWith(MockitoExtension.class)
public class MockitoTest {

    @Mock
    UserService mock;

    @Test
    void when_thenReturn() {
        when(mock.getName()).thenReturn("mockito");
        assertEquals(mock.getName(), "mockito");

        when(mock.greet("Tom")).thenReturn("Hi Tom");
        assertEquals(mock.greet("Tom"), "Hi Tom");

        when(mock.greet(anyString())).thenReturn("Hello!");
        assertEquals(mock.greet("mockito"), "Hello!");

        doAnswer(inv -> {
            System.out.println("doAnswer 메서드 호출됨!");
            return "doAnswer";
        }).when(mock).getName();
        assertEquals(mock.getName(), "doAnswer");
    }

    @Test
    void when_ThenReturn_ConsecutiveCall() {
        when(mock.getCount()).thenReturn(1, 2, 3);
        assertEquals(mock.getCount(), 1);
        assertEquals(mock.getCount(), 2);
        assertEquals(mock.getCount(), 3);
    }

    @Test
    void when_thenThrow() {
        when(mock.getName()).thenThrow(new IllegalStateException("예외 발생!"));
        assertThrows(IllegalStateException.class, () -> {mock.getName();});

        doThrow(new IllegalStateException()).when(mock).getName();
        assertThrows(IllegalStateException.class, () -> {mock.getName();});
    }
}

 

기본 동작 (Mock을 생성했지만 Stubbing하지 않았을 때)
타입 기본 반환값 설명
객체 (reference type) null
일반 객체 타입은 기본적으로 null 반환
Optional<T> Optional.empty()
Optional 타입은 비어 있는 Optional 반환
기본형 (int, boolean 등) 0, false, 0.0f, 0.0d, etc
기본형 타입은 해당 타입의 기본값 반환
컬렉션 (List, Set 등) 빈 컬렉션 ([], {} 등)
리스트, 세트 등은 비어 있는 컬렉션 반환
void 메서드 아무 일도 안 함
예외 없이 그냥 실행됨 (noop)

 

 

예제

더보기
@ExtendWith(MockitoExtension.class)
public class NoStubbingTest {

    @Mock
    UserService userService;

    @Test
    void defaultBehavior() {
        assertNull(userService.getName());

        assertEquals(Optional.empty(), userService.getOptionalName());

        assertEquals(0, userService.getAge());
        assertFalse(userService.isActive());

        assertTrue(userService.getRoles().isEmpty());

        userService.logout();
    }

}

 

Mock 객체 사용 검증 (Verify)

기능 메서드 / API 설명
호출 횟수 확인
verify(mock, times(n))
정확히 n번 호출되었는지 확인
verify(mock, atLeastOnce())
최소 한 번 이상 호출되었는지 확인
verify(mock, never())
전혀 호출되지 않았는지 확인
호출 순서 확인 InOrder inOrder = inOrder(...)
여러 Mock의 메서드가 특정 순서로 호출되었는지 확인
호출 타이밍 검증 verify(mock, timeout(ms))
지정한 시간 내에 호출되었는지 확인 (ms 밀리초 단위)
불필요한 호출 확인 verifyNoMoreInteractions(...)
지정된 호출 외에 다른 호출이 없었는지 확인 (불필요한 호출 확인용)

 

예제

더보기

@Test
void verify_call_times() {
    mock.getName();
    mock.getName();

    verify(mock, times(2)).getName();

    verify(mock, atLeastOnce()).getName();
    verify(mock, never()).greet("mockito");
}

@Test
void verify_inorder() {
    UserService mock1 = mock(UserService.class);
    UserService mock2 = mock(UserService.class);

    mock1.getName();
    mock2.getCount();

    InOrder inOrder = inOrder(mock1, mock2);

    inOrder.verify(mock1).getName();
    inOrder.verify(mock2).getCount();
}

@Test
void verify_timeout() {
    new Thread(() -> {
        try {
            Thread.sleep(50);
            mock.getCount();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();

    verify(mock, timeout(100)).getCount();
}

@Test
void verify_no_more_interactions() {
    mock.getCount();

    verify(mock).getCount();
    verifyNoMoreInteractions(mock);
}

 

 

4. BDD Mockito

BDD

  • 행위 중심 개발 (Behavior-Driven Development)
  • 소프트웨어가 어떻게 행동해야 하는지를 중심으로 개발을 이끌어가는 방법
  • 사용자의 관점에서 기능을 정의하고, 그 정의를 바탕으로 테스트를 작성함

 

핵심 요소
구성 요소 설명
Story
사용자 시나리오 중심 설명 (Narrative)
Given
어떤 조건이 주어졌을 때 (상태, 전제조건 등)
When
어떤 행동(이벤트)이 일어나면
Then
어떤 결과가 나와야 한다

 

BDD API

역할 BDD API 설명
Stubbing (Given)
given(mock.method()).willReturn(value)
특정 호출이 일어날 때 값을 반환하도록 설정
given(mock.method()).willThrow(exception)
호출 시 예외를 던지도록 설정
willAnswer(answer).given(mock).method(...)
호출 시 사용자 정의 동작을 실행
Verification (Then)
then(mock).should().method()
특정 메소드가 호출됐는지 검증
then(mock).should(times(n)).method()
특정 메소드가 n번 호출됐는지 검증
then(mock).shouldHaveNoMoreInteractions()
더 이상의 상호작용이 없었는지 검증
then(mock).should(never()).method()
특정 메소드가 절대 호출되지 않았는지 검증
호출 순서 검증
InOrder inOrder = inOrder(mock1, mock2)
여러 mock 간 호출 순서 검증 시작
then(mock).should(inOrder).method()
특정 호출 순서를 따라 호출되었는지 검증
호출 타이밍 검증 then(mock).should(timeout(100)).method()
100ms 이내에 호출되었는지 확인
더 이상 호출되지 않음 then(mock).shouldHaveNoMoreInteractions()
더 이상의 호출이 없었는지 확인

 

예제

더보기
@Test
void bdd_given_willReturn() {
    given(mock.getName()).willReturn("mockito");
    assertEquals(mock.getName(), "mockito");
}

@Test
void bdd_given_willThrow() {
    given(mock.getName()).willThrow(new IllegalStateException("예외 발생!"));
    assertThrows(IllegalStateException.class, () -> {mock.getName();});
}

@Test
void bdd_should() {
    mock.getCount();

    then(mock).should().getCount();
}

@Test
void bdd_should_times() {
    mock.getCount();
    mock.getCount();

    then(mock).should(times(2)).getCount();
}

@Test
void bdd_should_have_no_more_interactions() {
    mock.getCount();
    mock.getCount();

    then(mock).should(times(2)).getCount();
    then(mock).shouldHaveNoMoreInteractions();
}

@Test
void bdd_should_another_method() {
    mock.getCount();

    then(mock).should(never()).getName();
}

@Test
void bdd_should_order() {
    UserService mock1 = mock(UserService.class);
    UserService mock2 = mock(UserService.class);

    mock1.getName();
    mock2.getCount();

    InOrder inOrder = inOrder(mock1, mock2);

    then(mock1).should(inOrder).getName();
    then(mock2).should(inOrder).getCount();
}

@Test
void bdd_should_timeout() {
    new Thread(() -> {
        try {
            Thread.sleep(50);
            mock.getCount();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();

    then(mock).should(timeout(100)).getCount();
}

 

 

출처