Mockito — 개념, 주요 개념 및 작동 원리

저자: IT Sectr 게시일: 2026-04-08 읽는 시간: 8 분

Mockito는 Java 및 Kotlin 단위 테스트에서 mock 개체를 만들기 위한 오픈 소스 프레임워크로, 테스트 대상 코드를 외부 종속성에서 격리할 수 있게 해줍니다. 이를 통해 개발자는 실제 리포지토리, API 클라이언트 및 데이터베이스를 미리 정의된 동작을 가진 제어 가능한 스텁으로 대체합니다. Mockito.org에 따르면, 이 라이브러리는 단위 테스트를 사용하는 Java 프로젝트의 60% 이상에서 사용됩니다.

주요 포인트

  • Mockito — 테스트에서 실제 종속성을 대체하는 mock 개체를 만들기 위한 라이브러리입니다.
  • Mock — 실제 컴포넌트의 동작을 시뮬레이션하는 스텁 개체입니다.
  • Stubbing — mock 메서드 호출 시 반환값을 구성하는 것입니다.
  • Verify — 특정 인수로 메서드가 호출되었는지 확인하는 것입니다.
  • @InjectMocks — 테스트 대상 개체에 mock 종속성을 자동으로 주입하는 것입니다.

Mockito란?

Mockito는 Java, Kotlin 및 기타 JVM 언어의 단위 테스트를 위해 mock 개체(스텁)를 만들기 위한 오픈 소스 라이브러리입니다. 테스트 실행을 담당하는 JUnit과 달리 Mockito는 격리 문제를 해결합니다 — 테스트 대상 클래스의 실제 종속성을 예측 가능한 개체로 대체합니다.

mock 없이 데이터베이스나 외부 API에 액세스하는 메서드를 테스트하려면 실제 환경 설정 — 데이터베이스 배포, 서버 시작 — 이 필요합니다. Mockito는 이러한 종속성을 고정된 동작을 가진 개체로 대체합니다: repository.findById(1) 메서드는 데이터베이스에 액세스하지 않고 항상 지정된 User 개체를 반환합니다.

Mockito의 아키텍처는 Proxy 패턴(인터페이스 및 클래스용)을 기반으로 합니다. 라이브러리는 지정된 유형에 대한 하위 클래스 또는 프록시를 생성하고 모든 메서드 호출을 가로채서 기본값이나 when().thenReturn()을 통해 설정된 값을 반환합니다.

Mockito 작동 방식

Mockito의 작동 원리는 mock 생성, 동작 구성(stubbing), 호출 확인(verification)의 세 가지 기본 작업을 기반으로 합니다. 각 작업은 org.mockito.Mockito 클래스의 정적 메서드를 사용합니다 — Maven Central 통계에 따르면 Java 생태계에서 가장 많이 다운로드된 클래스입니다. 모든 mock 메서드 호출은 메모리에 기록되며, 나중에 verify를 통해 확인할 수 있습니다.

Mockito를 사용한 테스트의 세 단계

Mockito를 사용한 일반적인 테스트는 세 단계로 구성됩니다: Arrange — when().thenReturn()을 통한 mock 생성 및 스텁 구성, Act — 테스트 대상 메서드 호출, Assert — assertEquals 및 verify(mock)을 통한 결과 확인. 이 접근 방식을 AAA(Arrange-Act-Assert)라고 합니다.

리포지토리 mock을 사용한 기본 예제

Mockito가 사용자 리포지토리를 대체하는 간단한 테스트를 살펴보겠습니다. when().thenReturn() 메서드는 findById 호출이 미리 준비된 User 개체를 반환하도록 mock을 구성합니다.

java
// 리포지토리 mock 생성
UserRepository mockRepo = mock(UserRepository.class);

// 동작 구성: findById(1)가 사용자 반환
when(mockRepo.findById(1)).thenReturn(new User("Alice"));

// 메서드가 실제로 호출되었는지 확인
User result = mockRepo.findById(1);
assertEquals("Alice", result.getName());
verify(mockRepo).findById(1);

mock 개체 만들기

Mockito는 mock을 만드는 두 가지 방법을 제공합니다: 정적 메서드 mock(Class)MockitoAnnotations.openMocks()를 통한 초기화가 있는 @Mock 애너테이션입니다. 첫 번째 방법은 하나 또는 두 개의 mock에 적합하고, 두 번째 방법은 종속성이 많을 때 편리합니다 — 애너테이션이 보일러플레이트 코드를 줄여줍니다.

정적 mock() 메서드 사용

mock() 메서드는 클래스를 받아 when().thenReturn()을 통해 구성할 수 있는 스텁 개체를 반환합니다. 구성되지 않은 모든 메서드는 기본값(숫자는 0, boolean은 false, 개체는 null)을 반환합니다.

java
ApiClient apiClient = mock(ApiClient.class);
Database database = mock(Database.class);

JUnit 5와 함께 @Mock 애너테이션 사용

@Mock 애너테이션과 @ExtendWith(MockitoExtension.class)를 함께 사용하면 테스트 클래스의 모든 필드에 대해 mock이 자동으로 생성됩니다. MockitoExtension은 각 테스트 전 초기화를 담당합니다.

java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    void getUserShouldReturnUserFromRepo() {
        when(userRepository.findById(1)).thenReturn(new User("Alice"));
        User result = userService.getUser(1);
        assertEquals("Alice", result.getName());
    }
}

Stubbing: mock 동작 구성

Stubbing은 특정 인수로 호출될 때 mock 메서드가 무엇을 반환해야 하는지 정의하는 프로세스입니다. 기본 구문: when(mock.method(args)).thenReturn(value). 다양한 시나리오를 위해 Mockito는 여러 then-메서드 변형을 제공합니다.

메서드목적
thenReturn(value)항상 지정된 값을 반환합니다
thenThrow(exception)호출 시 예외를 throw합니다
thenAnswer(answer)반환값을 동적으로 계산합니다
thenCallRealMethod()실제 메서드를 호출합니다(부분 mock)

thenAnswer를 통한 동적 응답

반환값이 호출 인수에 따라 달라지는 경우 람다와 함께 thenAnswer를 사용합니다. 이는 실제 데이터 작업을 시뮬레이션하는 데 유용합니다 — 예를 들어, 전달된 개체를 기반으로 ID를 생성하는 경우입니다.

java
when(repository.save(any())).thenAnswer(invocation -> {
    User user = invocation.getArgument(0);
    user.setId(42);
    return user;
});

Verify: mock과의 상호작용 확인

Verify는 이전 mock 개체 라이브러리(EasyMock, jMock)에서는 제공하지 않는 Mockito의 고유한 기능입니다.

Verify는 반환값뿐만 아니라 부작용 — 결과를 반환하지 않는 메서드(void 메서드)의 호출 — 도 확인하므로 테스트를 더 안정적으로 만듭니다. verify(mock).methodName(args) 메서드는 특정 mock 메서드가 지정된 인수로 호출되었는지 확인합니다. 이를 통해 결과뿐만 아니라 프로세스 — 종속성에 액세스한 사실 — 도 테스트할 수 있습니다.

호출 횟수 확인

기본적으로 verify는 메서드가 정확히 한 번 호출되었는지 확인합니다. 다른 횟수가 필요한 경우 times(n), atLeast(n), never()Mockito 클래스의 기타 수정자를 사용합니다.

java
// 호출 횟수 확인
verify(repository, times(3)).save(any());
verify(repository, never()).delete(any());
verify(repository, atLeastOnce()).findById(1);

// 호출 순서 확인
InOrder inOrder = inOrder(repository);
inOrder.verify(repository).save(any());
inOrder.verify(repository).flush();

인수 캡처를 위한 ArgumentCaptor

메서드가 정확히 어떤 개체와 함께 호출되었는지 확인해야 하는 경우 ArgumentCaptor를 사용합니다. 호출 중에 인수 값을 캡처하고 해당 필드를 개별적으로 확인할 수 있습니다. ArgumentCaptor는 테스트 대상 코드가 내부적으로 개체를 만들어 종속성에 전달할 때 특히 유용합니다 — 그렇지 않으면 해당 개체를 확인할 수 없습니다.

java
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(repository).save(captor.capture());
assertEquals("Alice", captor.getValue().getName());

@Mock 및 @InjectMocks 애너테이션

@Mock@InjectMocks는 보일러플레이트 코드를 크게 줄여주는 두 가지 주요 Mockito 애너테이션입니다. @Mock은 필드에 대한 mock을 생성하고, @InjectMocks는 테스트 클래스의 모든 mock을 생성자, 세터 또는 필드를 통해 테스트 대상 개체에 주입합니다.

@InjectMocks 메커니즘은 다음 순서로 종속성 주입을 시도합니다: 가장 많은 인수를 가진 생성자, 유형별 세터, 비공개 필드. 이러한 방법 중 어느 것도 작동하지 않으면 개체는 null 종속성으로 남아 있고 테스트는 NullPointerException과 함께 실패합니다.

@InjectMocks 사용 규칙

중요한 점: @InjectMocks는 필드 유형을 분석하지 않습니다 — 유형이 호환되는 모든 mock을 대체합니다. 클래스에 동일한 유형의 필드가 두 개 있는 경우 Mockito가 잘못된 mock을 주입할 수 있습니다. 이러한 경우 mock 매개변수가 있는 명시적 생성자를 사용하는 것이 좋습니다.

Android 프로젝트에서의 Mockito

Android 개발에서 Mockito는 JUnit과 함께 ViewModel, Repository 및 UseCase를 테스트하는 데 사용됩니다. 이러한 클래스는 Android 컨텍스트 없이 JVM에서 실행되므로 Mockito는 해당 종속성 — Room DAO, Retrofit API, SharedPreferences — 을 예측 가능한 동작을 가진 스텁으로 대체합니다.

Mockito를 위한 Gradle 설정

Android 프로젝트에 Mockito를 추가하려면 mockito-core 또는 mockito-inline 종속성을 추가하기만 하면 됩니다(후자는 최종 클래스 및 정적 메서드의 mock을 지원합니다). 버전 5.12.0(2024)에는 Java 21 지원과 개선된 JUnit 5 통합이 포함되어 있습니다.

kotlin
// build.gradle.kts (module)
dependencies {
    testImplementation("org.mockito:mockito-core:5.12.0")
    testImplementation("org.mockito:mockito-junit-jupiter:5.12.0")
}

Mockito와 PowerMock: 구식 방식

이전에는 정적 메서드와 생성자의 mock을 위해 PowerMock — 바이트코드 계측을 통해 작동하는 확장 — 이 필요했습니다. Mockito 5.x 이상에서 mockito-inline을 사용하면 이 기능이 직접 내장되어 있습니다: mockStatic(ClassName.class)를 사용하면 추가 라이브러리 없이 정적 메서드를 mock할 수 있습니다.

Mockito로 ViewModel 테스트

일반적인 시나리오: ViewModel이 리포지토리 메서드를 호출하고 결과를 UI 상태로 변환합니다. Mockito가 리포지토리를 대체하고 테스트는 ViewModel이 성공 응답과 오류를 모두 올바르게 처리하는지 확인합니다. Clean Architecture를 사용할 때 각 계층(DataSource, Repository, UseCase)에 대해 mock이 생성됩니다 — 이를 통해 각 계층을 개별적으로 테스트할 수 있습니다.

  • 성공 케이스 — when(repo.getData()).thenReturn(Result.success(data)) → state = Success(data) 확인.
  • 오류 케이스 — when(repo.getData()).thenReturn(Result.error(exception)) → state = Error(message) 확인.
  • 로딩 상태 — ViewModel이 리포지토리를 호출하기 전에 isLoading = true를 설정했는지 verify로 확인.

자주 묻는 질문

Mockito와 MockK의 차이점은 무엇인가요?

Mockito는 프록시와 리플렉션을 사용하는 Java 및 Kotlin용 라이브러리입니다. MockK는 추가 구성 없이 코루틴, 확장 함수 및 최종 클래스를 지원하는 Kotlin 우선 라이브러리입니다.

Mockito에서 최종 클래스의 mock을 만드는 방법은?

Mockito 2.1부터 최종 클래스의 mock은 옵트인을 통해 지원됩니다. 버전 5.x(mockito-inline)에서는 기본적으로 활성화되어 있습니다. mockito-inline 종속성을 추가하고 표준 mock() 메서드를 사용하기만 하면 됩니다.

Mockito에서 Spy란 무엇인가요?

Spy는 기본적으로 실제 메서드를 호출하지만 when().thenReturn()을 통해 일부 메서드를 재정의할 수 있는 부분 mock입니다. Spy는 전체 클래스를 다시 작성할 수 없는 레거시 코드를 테스트하는 데 유용합니다.

thenReturn과 thenAnswer의 차이점은 무엇인가요?

thenReturn은 인수에 관계없이 항상 동일한 값을 반환합니다. thenAnswer는 호출 — 호출 인수, mock 자체 및 상태 — 를 기반으로 반환값을 계산합니다. 동적 응답의 경우 항상 thenAnswer를 사용하세요.

mock을 사용한 테스트에서 verify가 중요한 이유는?

Verify는 결과뿐만 아니라 프로세스 — 종속성에 액세스한 사실 — 도 확인합니다. 이는 데이터를 저장하거나 알림을 보내야 하는 서비스에 중요합니다. verify가 없으면 테스트는 메서드가 save() 또는 send()를 호출하지 않았음을 감지할 수 없습니다.

요약

  • Mockito — mock 개체를 만들기 위한 라이브러리, Java 및 Kotlin에서 mock의 사실상 표준입니다.
  • Mocks은 mock(Class) 또는 MockitoExtension과 함께 @Mock 애너테이션을 통해 생성됩니다.
  • Stubbing(when().thenReturn())은 mock 메서드 동작을 정의합니다.
  • Verify는 지정된 인수로 mock 메서드 호출의 사실과 횟수를 확인합니다.
  • @InjectMocks는 테스트 대상 개체에 mock을 자동으로 주입합니다.
  • Android 통합 — Mockito는 ViewModel, Repository 및 UseCase를 테스트하는 데 사용됩니다.
  • ArgumentCaptor는 개체 필드의 세부 확인을 위해 호출 인수를 캡처합니다.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기