2015/04/28

Android : AndroidStudio + PowerMock でstaticメソッドをmockする

確認環境

  • Android Studio 1.1
  • Android Gradle Plugin 1.1
  • JUnit 4.12
  • PowerMock 1.6.2

はじめに

Android Studio 1.1(android gradle plugin 1.1)からUnitTestがサポートされた.
今回はPowerMockを使ってstatic methodをモックするUnitTestを書いた.

Android StudioでUnitTestを開始するには下記を参考.

UnitTestの準備が整ったならモジュールのbuild.gradleに下記の依存ライブラリを記述する.

    // UnitTestにはtestCompileキーワードを使用
    testCompile 'junit:junit:4.12'

    // PowerMockのライブラリを指定
    testCompile 'org.powermock:powermock-module-junit4:1.6.2'
    testCompile 'org.powermock:powermock-api-mockito:1.6.2'

次はoptionだが, Android StudioのUnitTestは実行時にandroid.jarのモックとなるmockable-android*.jarを作成する.
ここで定義されるメソッドスタブはすべてRuntimeExceptionをスローするように実装される.
これをExceptionではなくDefault value(false etc.)を返却するように変更するには下記を指定する.

    testOptions {
        unitTests.returnDefaultValues = true
    }

あとはいつもの方法でPowerMockを使えばOK.

  • TestRunnerにPowerMockRunnerを指定
    @RunWith(PowerMockRunner.class)
    public class MyTestCase extends TestCase {
  • TestCaseを継承したクラスにアノテーションを追加
    @RunWith(PowerMockRunner.class)
    @PrepareForTest(Static.class) // Staticメソッドをモックするクラスを指定(,で複数指定可)
    public class MyTestCase extends TestCase {

テストコード

    @Test
    public void testMockStaticMethod() throws Exception {
        assertFalse("オリジナルはfalseを返す", Target.staticBoolean());

        PowerMockito.mockStatic(Target.class);
        when(Target.staticBoolean()).thenReturn(true);
        assertTrue("モックはtrueを返す", Target.staticBoolean());
        verifyStatic(times(1));
    }

    @Test
    public void testMockConstructor() throws Exception {
        assertEquals("Valueは引数なしのコンストラクタで初期化される",
                new Value(), new Target().getValue());

        Value mockValue = new Value(100);
        assertNotEquals("引数ありで初期化されたValueとでは等価ではなくなる",
                mockValue, new Target().getValue());

        PowerMockito.whenNew(Value.class).withNoArguments().thenReturn(mockValue);
        assertEquals("Valueの引数なしコンストラクタでモックが返される",
                mockValue, new Target().getValue());
        verifyNew(Value.class).withNoArguments();

        mockValue = new Value(500);
        assertEquals("Valueの引数ありコンストラクタはモックされていない",
                mockValue, new Value(500));

        mockValue = new Value(1000);
        PowerMockito.whenNew(Value.class).withArguments(anyInt()).thenReturn(mockValue);
        assertEquals("Valueの引数ありコンストラクタもモックされる",
                mockValue, new Target(500).getValue());
        verifyNew(Value.class, atLeastOnce()).withArguments(anyInt());
    }

    @Test
    public void testLogger() throws Exception {
        PowerMockito.mockStatic(Log.class);
        Mockito.when(Log.isLoggable(anyString(), anyInt())).thenReturn(true);
        assertTrue("モックしてtrueを返す", Log.isLoggable("mocktag", Log.VERBOSE));

        assertTrue(BuildConfig.DEBUG);

        // static finalな定数をreflectionで変更.
        Field field = BuildConfig.class.getField("DEBUG");
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, false);

        assertFalse(BuildConfig.DEBUG);
    }

以上.