Mockito는 Java에서 널리 사용되는 모킹 프레임워크(Mock Framework)입니다. 이는 단위 테스트(Unit Test)에서 의존성 객체를 가짜(Mock)로 만들어 실제 객체 없이 테스트할 수 있도록 도와줍니다. 주로 Spring과 같은 의존성 주입 기반의 프레임워크에서 많이 사용되며, 객체의 복잡한 의존성을 관리하면서도 테스트를 쉽게 작성할 수 있게 합니다.
Mockito는 단위 테스트를 작성할 때 실제 객체를 대신해 동작하는 "모의 객체(Mock Object)"를 생성하고 제어할 수 있는 라이브러리입니다.
이 모의 객체는 실제 객체와 동일한 인터페이스를 따르지만, 동작을 우리가 정의한 대로 설정할 수 있습니다. 이렇게 하면 의존성 객체와의 상호작용을 쉽게 테스트할 수 있으며, 테스트 환경을 제어하기 용이해집니다.
예를 들어, 데이터베이스나 외부 API를 호출하는 부분이 있을 때 실제로 이를 테스트하기 위해 네트워크나 데이터베이스에 연결할 필요 없이 Mockito로 가짜 객체를 만들어 테스트할 수 있습니다.
Mockito는 실제 객체 대신 사용할 모의 객체를 생성합니다. mock() 메서드를 사용하여 간단하게 객체를 만들 수 있습니다.
import static org.mockito.Mockito.*;
import org.junit.Test;
public class SampleTest {
@Test
public void testMock() {
// List 인터페이스의 Mock 객체 생성
List<String> mockedList = mock(List.class);
// Mock 객체 사용
mockedList.add("Hello");
verify(mockedList).add("Hello"); // add 메서드가 호출됐는지 검증
}
}
모의 객체의 특정 메서드 호출 시 반환될 값을 설정할 수 있습니다. 이를 Stubbing이라고 부릅니다.
import static org.mockito.Mockito.*;
public class SampleTest {
@Test
public void testStubbing() {
// Mock 객체 생성
List<String> mockedList = mock(List.class);
// Stubbing: size() 메서드가 호출되면 100을 반환하도록 설정
when(mockedList.size()).thenReturn(100);
// 검증
assertEquals(100, mockedList.size());
}
}
Mockito는 메서드가 호출되었는지, 몇 번 호출되었는지 등의 검증을 지원합니다. verify() 메서드를 사용하여 특정 메서드가 호출되었는지 검증할 수 있습니다.
import static org.mockito.Mockito.*;
public class SampleTest {
@Test
public void testVerification() {
// Mock 객체 생성
List<String> mockedList = mock(List.class);
// Mock 객체 사용
mockedList.add("Mockito");
mockedList.clear();
// 호출 여부 검증
verify(mockedList).add("Mockito"); // add("Mockito")가 호출되었는지 확인
verify(mockedList).clear(); // clear()가 호출되었는지 확인
}
}
메서드가 몇 번 호출되었는지 검증할 수 있습니다.
import static org.mockito.Mockito.*;
public class SampleTest {
@Test
public void testCallCount() {
List<String> mockedList = mock(List.class);
// Mock 객체 사용
mockedList.add("Mockito");
mockedList.add("Mockito");
// 호출 횟수 검증
verify(mockedList, times(2)).add("Mockito"); // add("Mockito")가 2번 호출되었는지 검증
}
}
Mockito를 사용하면 특정 메서드가 호출될 때 예외를 던지도록 설정할 수 있습니다.
import static org.mockito.Mockito.*;
import java.util.List;
public class SampleTest {
@Test(expected = RuntimeException.class)
public void testExceptionThrowing() {
List<String> mockedList = mock(List.class);
// 특정 메서드 호출 시 예외를 던지도록 설정
when(mockedList.get(0)).thenThrow(new RuntimeException());
// 예외가 발생하는지 확인
mockedList.get(0); // 호출 시 RuntimeException 발생
}
}
@RunWith(MockitoJUnitRunner.class)
public class SampleTest {
@Mock
private List<String> mockedList;
@Test
public void testWithMockitoRunner() {
mockedList.add("Hello");
verify(mockedList).add("Hello");
}
}
이와 같이 @Mock 어노테이션을 사용하여 모의 객체를 선언할 수 있고, MockitoJUnitRunner를 통해 테스트 케이스에서 쉽게 주입할 수 있습니다.
Mockito를 사용해 모의 객체로 테스트하는 것이 유리한 경우는 다음과 같습니다:
외부 의존성이 복잡하거나 불안정한 경우: 실제 객체가 데이터베이스, 네트워크, 파일 시스템 등 외부 리소스와 연결될 때는 테스트가 복잡해지고 비효율적이 될 수 있습니다. 예를 들어, 네트워크 응답 시간이 일정하지 않거나 데이터베이스 설정에 따라 테스트 결과가 달라질 수 있습니다. 이때, 모의 객체를 사용해 이러한 의존성을 제거함으로써 테스트를 보다 독립적이고 일관되게 만들 수 있습니다.
테스트 속도 향상: 외부 의존성이 많은 시스템에서는 테스트 실행 시간이 오래 걸릴 수 있습니다. Mockito를 사용하면 의존성 객체들을 모의로 대체하여 속도 문제를 해결할 수 있습니다. 특히 대규모 애플리케이션의 경우, 전체 테스트 시간을 크게 단축할 수 있습니다.
예외 처리 시나리오 테스트: 실제 객체를 사용하면 발생하기 어려운 예외 상황을 테스트하기가 어렵습니다. 예를 들어, 네트워크 타임아웃이나 DB 연결 실패 같은 상황은 일관되게 재현하기가 어렵습니다. 하지만 Mockito를 통해 의도적으로 예외를 발생시켜 이러한 시나리오를 쉽게 테스트할 수 있습니다.
단위 테스트 목적: 단위 테스트는 각 클래스나 메서드를 독립적으로 테스트하는 것이 목적입니다. 만약 하나의 클래스가 다른 클래스나 서비스에 의존하고 있다면, 모의 객체를 통해 의존성을 제거하여 해당 클래스의 로직에만 집중할 수 있습니다.
Mockito를 사용해 복잡한 서비스 레이어를 테스트할 때는 몇 가지 모범 사례를 따르는 것이 중요합니다:
Mocking은 필요한 부분만 최소화: 지나치게 많은 모의 객체를 만들면 테스트 코드가 복잡해지고 유지보수가 어려워질 수 있습니다. 꼭 필요한 부분에서만 모의 객체를 사용하고, 가능한 한 실제 로직을 활용하는 것이 좋습니다. 예를 들어, 데이터베이스 액세스나 외부 API 호출처럼 실제 환경에 의존적인 부분만 모킹하고, 내부 비즈니스 로직은 실제로 테스트하는 것이 이상적입니다.
간결한 Stubbing 사용: Mock 객체의 동작을 설정할 때는 가능하면 간결하게 작성하는 것이 좋습니다. 너무 복잡한 Stubbing 설정은 테스트 코드를 이해하기 어렵게 만듭니다. 또한 불필요한 Stubbing을 피하고, 테스트에서 직접적으로 사용되는 메서드만 Stubbing하도록 합니다.
when(mockObject.someMethod()).thenReturn(someValue);
verify(dependencyObject, times(1)).someMethod();
테스트 격리: 각 테스트는 독립적이어야 하며, 다른 테스트에 영향을 주지 않도록 해야 합니다. 이를 위해 Mock 객체의 상태가 테스트 사이에서 공유되지 않도록 설정하고, 각 테스트가 끝날 때 Mock 객체를 초기화하는 것이 좋습니다.
예외 처리도 테스트: 서비스 레이어에서 중요한 것은 예외 처리입니다. 모의 객체를 통해 의도적으로 예외를 발생시킨 후, 서비스 레이어가 이를 어떻게 처리하는지 검증하는 테스트를 작성하는 것이 중요합니다. 예를 들어, 데이터베이스 연결 실패나 잘못된 입력에 대해 적절한 에러 메시지나 처리 로직이 있는지 확인하는 것이 필요합니다.
Mockito는 매우 강력한 모킹 프레임워크이지만, 다른 모킹 프레임워크가 더 적합한 경우도 있습니다. 다음과 같은 상황에서는 Mockito 외의 프레임워크를 고려해볼 수 있습니다:
PowerMockito.mockStatic(SomeClass.class);
컨스트럭터 모킹(Constructor Mocking): Mockito는 기본적으로 객체의 생성자를 모킹할 수 없습니다. 생성자를 통한 객체 생성이나 내부 로직을 제어해야 할 경우, 역시 PowerMock이 유리합니다. 이 경우에도 PowerMock은 생성자 호출을 모킹할 수 있는 기능을 제공합니다.
기존 레거시 코드 테스트: 레거시 코드베이스에서는 설계가 모킹에 적합하지 않을 수 있습니다. 특히 의존성이 직접적으로 주입되지 않고 내부에서 객체를 생성하는 경우에는 PowerMock과 같은 더 강력한 모킹 프레임워크가 필요할 수 있습니다. Mockito로는 이런 복잡한 구조를 모킹하기 어려울 수 있기 때문입니다.
Mocking 범위를 넘어서 요구되는 기능: Mockito는 기본적으로 객체의 메서드 호출을 검증하는 데 중점을 둡니다. 반면, Spock과 같은 프레임워크는 BDD(Behavior-Driven Development) 스타일로 좀 더 직관적이고 선언적인 테스트 작성이 가능합니다. 테스트 코드가 보다 읽기 쉽고 깔끔하게 작성되길 원한다면 Spock 같은 프레임워크를 선택할 수 있습니다.
다양한 JVM 언어 지원: Mockito는 주로 Java를 지원하지만, Spock은 Groovy와 잘 호환되며, 다양한 JVM 언어 환경에서도 사용될 수 있습니다. 따라서 Groovy 기반의 프로젝트에서는 Spock을 선호할 수 있습니다.