Mockito는 Java 및 Kotlin 단위 테스트에서 mock 개체를 만들기 위한 오픈 소스 프레임워크로, 테스트 대상 코드를 외부 종속성에서 격리할 수 있게 해줍니다. 이를 통해 개발자는 실제 리포지토리, API 클라이언트 및 데이터베이스를 미리 정의된 동작을 가진 제어 가능한 스텁으로 대체합니다. Mockito.org에 따르면, 이 라이브러리는 단위 테스트를 사용하는 Java 프로젝트의 60% 이상에서 사용됩니다.
주요 포인트
Mockito는 Java, Kotlin 및 기타 JVM 언어의 단위 테스트를 위해 mock 개체(스텁)를 만들기 위한 오픈 소스 라이브러리입니다. 테스트 실행을 담당하는 JUnit과 달리 Mockito는 격리 문제를 해결합니다 — 테스트 대상 클래스의 실제 종속성을 예측 가능한 개체로 대체합니다.
mock 없이 데이터베이스나 외부 API에 액세스하는 메서드를 테스트하려면 실제 환경 설정 — 데이터베이스 배포, 서버 시작 — 이 필요합니다. Mockito는 이러한 종속성을 고정된 동작을 가진 개체로 대체합니다: repository.findById(1) 메서드는 데이터베이스에 액세스하지 않고 항상 지정된 User 개체를 반환합니다.
Mockito의 아키텍처는 Proxy 패턴(인터페이스 및 클래스용)을 기반으로 합니다. 라이브러리는 지정된 유형에 대한 하위 클래스 또는 프록시를 생성하고 모든 메서드 호출을 가로채서 기본값이나 when().thenReturn()을 통해 설정된 값을 반환합니다.
Mockito의 작동 원리는 mock 생성, 동작 구성(stubbing), 호출 확인(verification)의 세 가지 기본 작업을 기반으로 합니다. 각 작업은 org.mockito.Mockito 클래스의 정적 메서드를 사용합니다 — Maven Central 통계에 따르면 Java 생태계에서 가장 많이 다운로드된 클래스입니다. 모든 mock 메서드 호출은 메모리에 기록되며, 나중에 verify를 통해 확인할 수 있습니다.
Mockito를 사용한 일반적인 테스트는 세 단계로 구성됩니다: Arrange — when().thenReturn()을 통한 mock 생성 및 스텁 구성, Act — 테스트 대상 메서드 호출, Assert — assertEquals 및 verify(mock)을 통한 결과 확인. 이 접근 방식을 AAA(Arrange-Act-Assert)라고 합니다.
Mockito가 사용자 리포지토리를 대체하는 간단한 테스트를 살펴보겠습니다. when().thenReturn() 메서드는 findById 호출이 미리 준비된 User 개체를 반환하도록 mock을 구성합니다.
// 리포지토리 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);
Mockito는 mock을 만드는 두 가지 방법을 제공합니다: 정적 메서드 mock(Class)와 MockitoAnnotations.openMocks()를 통한 초기화가 있는 @Mock 애너테이션입니다. 첫 번째 방법은 하나 또는 두 개의 mock에 적합하고, 두 번째 방법은 종속성이 많을 때 편리합니다 — 애너테이션이 보일러플레이트 코드를 줄여줍니다.
mock() 메서드는 클래스를 받아 when().thenReturn()을 통해 구성할 수 있는 스텁 개체를 반환합니다. 구성되지 않은 모든 메서드는 기본값(숫자는 0, boolean은 false, 개체는 null)을 반환합니다.
ApiClient apiClient = mock(ApiClient.class);
Database database = mock(Database.class);
@Mock 애너테이션과 @ExtendWith(MockitoExtension.class)를 함께 사용하면 테스트 클래스의 모든 필드에 대해 mock이 자동으로 생성됩니다. MockitoExtension은 각 테스트 전 초기화를 담당합니다.
@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 메서드가 무엇을 반환해야 하는지 정의하는 프로세스입니다. 기본 구문: when(mock.method(args)).thenReturn(value). 다양한 시나리오를 위해 Mockito는 여러 then-메서드 변형을 제공합니다.
| 메서드 | 목적 |
|---|---|
| thenReturn(value) | 항상 지정된 값을 반환합니다 |
| thenThrow(exception) | 호출 시 예외를 throw합니다 |
| thenAnswer(answer) | 반환값을 동적으로 계산합니다 |
| thenCallRealMethod() | 실제 메서드를 호출합니다(부분 mock) |
반환값이 호출 인수에 따라 달라지는 경우 람다와 함께 thenAnswer를 사용합니다. 이는 실제 데이터 작업을 시뮬레이션하는 데 유용합니다 — 예를 들어, 전달된 개체를 기반으로 ID를 생성하는 경우입니다.
when(repository.save(any())).thenAnswer(invocation -> {
User user = invocation.getArgument(0);
user.setId(42);
return user;
});
Verify는 이전 mock 개체 라이브러리(EasyMock, jMock)에서는 제공하지 않는 Mockito의 고유한 기능입니다.
Verify는 반환값뿐만 아니라 부작용 — 결과를 반환하지 않는 메서드(void 메서드)의 호출 — 도 확인하므로 테스트를 더 안정적으로 만듭니다. verify(mock).methodName(args) 메서드는 특정 mock 메서드가 지정된 인수로 호출되었는지 확인합니다. 이를 통해 결과뿐만 아니라 프로세스 — 종속성에 액세스한 사실 — 도 테스트할 수 있습니다.
기본적으로 verify는 메서드가 정확히 한 번 호출되었는지 확인합니다. 다른 횟수가 필요한 경우 times(n), atLeast(n), never() 및 Mockito 클래스의 기타 수정자를 사용합니다.
// 호출 횟수 확인
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<User> captor = ArgumentCaptor.forClass(User.class);
verify(repository).save(captor.capture());
assertEquals("Alice", captor.getValue().getName());
@Mock과 @InjectMocks는 보일러플레이트 코드를 크게 줄여주는 두 가지 주요 Mockito 애너테이션입니다. @Mock은 필드에 대한 mock을 생성하고, @InjectMocks는 테스트 클래스의 모든 mock을 생성자, 세터 또는 필드를 통해 테스트 대상 개체에 주입합니다.
@InjectMocks 메커니즘은 다음 순서로 종속성 주입을 시도합니다: 가장 많은 인수를 가진 생성자, 유형별 세터, 비공개 필드. 이러한 방법 중 어느 것도 작동하지 않으면 개체는 null 종속성으로 남아 있고 테스트는 NullPointerException과 함께 실패합니다.
중요한 점: @InjectMocks는 필드 유형을 분석하지 않습니다 — 유형이 호환되는 모든 mock을 대체합니다. 클래스에 동일한 유형의 필드가 두 개 있는 경우 Mockito가 잘못된 mock을 주입할 수 있습니다. 이러한 경우 mock 매개변수가 있는 명시적 생성자를 사용하는 것이 좋습니다.
Android 개발에서 Mockito는 JUnit과 함께 ViewModel, Repository 및 UseCase를 테스트하는 데 사용됩니다. 이러한 클래스는 Android 컨텍스트 없이 JVM에서 실행되므로 Mockito는 해당 종속성 — Room DAO, Retrofit API, SharedPreferences — 을 예측 가능한 동작을 가진 스텁으로 대체합니다.
Android 프로젝트에 Mockito를 추가하려면 mockito-core 또는 mockito-inline 종속성을 추가하기만 하면 됩니다(후자는 최종 클래스 및 정적 메서드의 mock을 지원합니다). 버전 5.12.0(2024)에는 Java 21 지원과 개선된 JUnit 5 통합이 포함되어 있습니다.
// build.gradle.kts (module)
dependencies {
testImplementation("org.mockito:mockito-core:5.12.0")
testImplementation("org.mockito:mockito-junit-jupiter:5.12.0")
}
이전에는 정적 메서드와 생성자의 mock을 위해 PowerMock — 바이트코드 계측을 통해 작동하는 확장 — 이 필요했습니다. Mockito 5.x 이상에서 mockito-inline을 사용하면 이 기능이 직접 내장되어 있습니다: mockStatic(ClassName.class)를 사용하면 추가 라이브러리 없이 정적 메서드를 mock할 수 있습니다.
일반적인 시나리오: ViewModel이 리포지토리 메서드를 호출하고 결과를 UI 상태로 변환합니다. Mockito가 리포지토리를 대체하고 테스트는 ViewModel이 성공 응답과 오류를 모두 올바르게 처리하는지 확인합니다. Clean Architecture를 사용할 때 각 계층(DataSource, Repository, UseCase)에 대해 mock이 생성됩니다 — 이를 통해 각 계층을 개별적으로 테스트할 수 있습니다.
자주 묻는 질문
Mockito는 프록시와 리플렉션을 사용하는 Java 및 Kotlin용 라이브러리입니다. MockK는 추가 구성 없이 코루틴, 확장 함수 및 최종 클래스를 지원하는 Kotlin 우선 라이브러리입니다.
Mockito 2.1부터 최종 클래스의 mock은 옵트인을 통해 지원됩니다. 버전 5.x(mockito-inline)에서는 기본적으로 활성화되어 있습니다. mockito-inline 종속성을 추가하고 표준 mock() 메서드를 사용하기만 하면 됩니다.
Spy는 기본적으로 실제 메서드를 호출하지만 when().thenReturn()을 통해 일부 메서드를 재정의할 수 있는 부분 mock입니다. Spy는 전체 클래스를 다시 작성할 수 없는 레거시 코드를 테스트하는 데 유용합니다.
thenReturn은 인수에 관계없이 항상 동일한 값을 반환합니다. thenAnswer는 호출 — 호출 인수, mock 자체 및 상태 — 를 기반으로 반환값을 계산합니다. 동적 응답의 경우 항상 thenAnswer를 사용하세요.
Verify는 결과뿐만 아니라 프로세스 — 종속성에 액세스한 사실 — 도 확인합니다. 이는 데이터를 저장하거나 알림을 보내야 하는 서비스에 중요합니다. verify가 없으면 테스트는 메서드가 save() 또는 send()를 호출하지 않았음을 감지할 수 없습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.