Mockito는 final과 static method를 mocking, spying하는 것을 지원하지 않습니다. 하지만, Dexmaker의 Mockito 라이브러리를 이용하면 안드로이드에서 final, static method를 mocking, spying 할 수 있습니다.
안드로이드 프로젝트에서 Dexmaker mockito를 설정하고, final, static method를 mocking하는 예제를 소개합니다.
Mockito를 이용하여 테스트 코드를 작성하는 기본적인 방법은 Android Mockito로 테스트 코드 작성하기를 참고해주세요.
프로젝트 설정
Dexmaker는 P OS 이상의 SDK(API 28)만 사용할 수 있습니다. P 이전 버전은 사용할 수 없습니다.
프로젝트를 만들면 App의 build.gradle
에 다음과 같이 Java 8
을 지원하도록 합니다.
android {
...
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
...
}
그리고 build.gradle
의 의존성에 다음과 같이 라이브러리를 추가합니다.
dexmaker는 내부적으로 org.mockito:mockito-core
를 참조하기 때문에 다른 라이브러리는 별도로 추가하지 않아도 됩니다.
dependencies {
...
androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito-inline-extended:2.25.1'
}
Mocking final method
final method를 mocking하는 예제입니다. 기존에 Mockito에서 non-final 메소드를 mocking하는 방법과 동일합니다.
class FinalTrojan {
final String finalOpen() { return "horse"; }
}
@Test
public void testStubbingFinalMethod() {
FinalTrojan mockF = mock(FinalTrojan.class);
when(mockF.finalOpen()).thenReturn("soldiers");
assertEquals("soldiers", mockF.finalOpen());
}
Mocking static method
staic method를 mocking하는 예제입니다.
private static class SuperClass {
final String returnA() {
return "superA";
}
static String returnB() {
return "superB";
}
static String returnC() {
return "superC";
}
}
@Test
public void mockingExample() {
assertEquals("superB", SuperClass.returnB()); // 1
MockitoSession session = mockitoSession()
.mockStatic(SuperClass.class).startMocking(); // 2
try{
assertNull(SuperClass.returnB()); // 3
when(SubClass.returnB()).thenReturn("fakeB"); // 4
assertEquals("fakeB", SuperClass.returnB()); // 5
} finally {
session.finishMocking(); // 6
}
}
- mocking하지 않으면 실제 클래스에서 구현된 값이 리턴됩니다.
- MockitoSession 객체로 클래스를 mocking합니다.
- mocking이 시작되면, 메소드는 null을 리턴합니다.
- when, thenReturn 을 이용하여 mocking을 구현합니다.
- 메소드를 호출했을 때, 설정한 값이 리턴되는지 확인합니다.
- mocking이 끝나면
finishMocking()
를 호출하여 종료해 줍니다.
mocking은
mockStatic(SuperClass.class)
처럼, spying은spyStatic(SuperClass.class)
처럼 사용하면 됩니다.
Mocking Subclass
Subclass를 mocking하는 예제입니다.
private static class SuperClass {
final String returnA() {
return "superA";
}
static String returnB() {
return "superB";
}
static String returnC() {
return "superC";
}
}
private static final class SubClass extends SuperClass {
static String recorded = null;
static String returnC() {
return "subC";
}
static final String record(String toRecord) {
recorded = toRecord;
return "record";
}
}
@Test
public void mockOverriddenStaticMethod() throws Exception {
MockitoSession session = mockitoSession().mockStatic(SubClass.class).startMocking();
try {
// By default all static methods of the mocked class should return the default answers
assertNull(SubClass.returnB());
assertNull(SubClass.returnC());
// Super class is not mocked
assertEquals("superB", SuperClass.returnB());
assertEquals("superC", SuperClass.returnC());
when(SubClass.returnB()).thenReturn("fakeB");
when(SubClass.returnC()).thenReturn("fakeC");
// Make sure behavior is changed
assertEquals("fakeB", SubClass.returnB());
assertEquals("fakeC", SubClass.returnC());
// Super class should not be affected
assertEquals("superB", SuperClass.returnB());
assertEquals("superC", SuperClass.returnC());
} finally {
session.finishMocking();
}
// Mocking should be stopped
assertEquals("superB", SubClass.returnB());
assertEquals("subC", SubClass.returnC());
}
인자가 있는 static method mocking
인자가 있는 static method를 mocking하는 예제입니다.
static class S {
static String staticEcho(String in) {
return in;
}
}
@Test
public void testStubbingStaticMethod() {
MockitoSession session = mockitoSession().mockStatic(S.class).startMocking();
try {
when(S.staticEcho("Marco")).thenReturn("Polo");
assertEquals("Polo", S.staticEcho("Marco"));
} finally {
session.finishMocking();
}
// Once the session is finished, all stubbings are reset
assertEquals("Marco", S.staticEcho("Marco"));
}
Verify static method
static method를 verify하는 방법입니다.
여기서는 mocking이 아니라 spying을 하였습니다. mocking은 클래스 내부에 구현된 메소드를 stub으로 만들지만, spying은 구현된 코드를 그대로 사용한다는 차이점이 있습니다.
verify는 지정한 메소드가 호출되었는지, 몇번 호출되었는지 확인하는 방법입니다. 행위(Behavior) 관점에서 테스트를 수행할 수 있습니다.
verify()
는 예상되는 횟수와 실제 호출되는 횟수가 다른 경우 테스트를 실패로 처리합니다.
다음은 UtilClass
클래스를 spying하고, static 메소드가 몇번 호출되는지 verify하는 코드입니다.
static class UtilClass {
public static String staticMethod(String str) {
return str;
}
public static void staticVoidMethod(String str) {
staticMethod(str);
}
}
@Test
public void testVerifyStaticMethod() {
MockitoSession session =
mockitoSession().spyStatic(UtilClass.class).startMocking();
try {
UtilClass.staticVoidMethod("string");
UtilClass.staticMethod("string");
verify(() -> UtilClass.staticMethod("string"), atLeastOnce());
verify(() -> UtilClass.staticMethod("string"), times(2));
verify(() -> UtilClass.staticVoidMethod("string"), atLeastOnce());
verify(() -> UtilClass.staticVoidMethod("string"), atMost(2));
} finally {
session.finishMocking();
}
}
verify는 () -> UtilClass.staticMethod()
와 같이 람다표현식으로 호출할 메소드를 전달합니다.
그리고 atLeastOnce()
와 같은 횟수 정보를 인자로 전달합니다.
아래 API들은 예상되는 메소드의 호출 횟수를 정의하며, 의미는 다음과 같습니다.
- atLeastOnce(): 메소드가 최소 한번 호출되었는지 확인
- times(wanted number): 원하는 수만큼 호출되었는지 확인
- atMost(max number): 최대 호출 횟수, 초과되면 테스트 실패
- atLeast(min number): 최소 호출 횟수, 미만이면 테스트 실패
Reset mock
Mocking한 것을 reset()
API로 초기화하는 예제입니다.
@Test
public void resetMock() throws Exception {
MockitoSession session =
mockitoSession().mockStatic(SuperClass.class).startMocking();
try {
assertNull(SuperClass.returnB());
when(SuperClass.returnB()).thenReturn("fakeB");
assertEquals("fakeB", SuperClass.returnB());
reset(staticMockMarker(SuperClass.class));
assertNull(SuperClass.returnB());
} finally {
session.finishMocking();
}
}
안드로이드 메소드에 적용
안드로이드의 Settings
의 메소드를 spying하는 예제입니다.
@Test
public void spyStatic() throws Exception {
ContentResolver resolver =
InstrumentationRegistry.getTargetContext().getContentResolver();
String deviceName = Settings.Global.getString(resolver, DEVICE_NAME);
MockitoSession session =
mockitoSession().spyStatic(Settings.Global.class).startMocking();
try {
// Cannot call when(Settings.getString(any(ContentResolver.class), eq("...")))
// as any(ContentResolver.class) returns null which makes getString fail. Hence need to
// use less lambda API
doReturn("23").when(() -> Settings.Global.getString(any
(ContentResolver.class), eq("twenty three")));
doReturn(42).when(() -> Settings.Global.getInt(any
(ContentResolver.class), eq("fourty two")));
// Make sure behavior is changed
assertEquals("23", Settings.Global.getString(resolver, "twenty three"));
assertEquals(42, Settings.Global.getInt(resolver, "fourty two"));
// Make sure non-mocked methods work as before
assertEquals(deviceName, Settings.Global.getString(resolver, DEVICE_NAME));
} finally {
session.finishMocking();
}
}
샘플
이 글에서 사용한 샘플은 GitHub - Sample에서 확인할 수 있습니다.
참고
Related Posts
- Android 14 - 사진/동영상 파일, 일부 접근 권한 소개
- Android - adb push, pull로 파일 복사, 다운로드
- Android 14 - 암시적 인텐트 변경사항 및 문제 해결
- Jetpack Compose - Row와 Column
- Android 13, AOSP 오픈소스 다운로드 및 빌드
- Android 13 - 세분화된 미디어 파일 권한
- Android 13에서 Notification 권한 요청, 알림 띄우기
- Android 13에서 'Access blocked: ComponentInfo' 에러 해결
- 에러 해결: android gradle plugin requires java 11 to run. you are currently using java 1.8.
- 안드로이드 - 코루틴과 Retrofit으로 비동기 통신 예제
- 안드로이드 - 코루틴으로 URL 이미지 불러오기
- Android - 진동, Vibrator, VibrationEffect 예제
- Some problems were found with the configuration of task 에러 수정
- Query method parameters should either be a type that can be converted into a database column or a List
- 우분투에서 Android 12 오픈소스 다운로드 및 빌드
- Android - ViewModel을 생성하는 방법
- Android - Transformations.map(), switchMap() 차이점
- Android - Transformations.distinctUntilChanged() 소개
- Android - TabLayout 구현 방법 (+ ViewPager2)
- Android - 휴대폰 전화번호 가져오는 방법
- Android 12 - Splash Screens 알아보기
- Android 12 - Incremental Install (Play as you Download) 소개
- Android - adb 명령어로 bugreport 로그 파일 추출
- Android - adb 명령어로 App 데이터 삭제
- Android - adb 명령어로 앱 비활성화, 활성화
- Android - adb 명령어로 특정 패키지의 PID 찾기
- Android - adb 명령어로 퍼미션 Grant 또는 Revoke
- Android - adb 명령어로 apk 설치, 삭제
- Android - adb 명령어로 특정 패키지의 프로세스 종료
- Android - adb 명령어로 screen capture 저장
- Android - adb 명령어로 System 앱 삭제, 설치
- Android - adb 명령어로 settings value 확인, 변경
- Android 12 - IntentFilter의 exported 명시적 선언
- Android - adb 명령어로 공장초기화(Factory reset)
- Android - adb logcat 명령어로 로그 출력