HOME > android > basic

Mockito - static, final method를 mocking하는 방법

By JS | 29 Jan 2020

Mockito는 final과 static method를 mocking, spying하는 것을 지원하지 않습니다. 하지만, Dexmaker의 Mockito 라이브러리를 이용하면 안드로이드에서 final, static method를 mocking, spying 할 수 있습니다.

안드로이드 프로젝트에서 Dexmaker mockito를 설정하고, final, static method를 mocking하는 예제를 소개합니다.

프로젝트 설정

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
    }
}
  1. mocking하지 않으면 실제 클래스에서 구현된 값이 리턴됩니다.
  2. MockitoSession 객체로 클래스를 mocking합니다.
  3. mocking이 시작되면, 메소드는 null을 리턴합니다.
  4. when, thenReturn 을 이용하여 mocking을 구현합니다.
  5. 메소드를 호출했을 때, 설정한 값이 리턴되는지 확인합니다.
  6. 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

인자가 있는 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"));
}

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();
    }
}

참고