Robolectric3.4.2+Powermock1.7.3でのテスト (2017版)

2017-09-29T18:00:00+09:00 Android robolectric

今まで何度か検証してみましたが、情報が2013年度と古く今で実際使えるのが微妙だったので2017年度版としてチャラい調査をしてみました

※コードのベースはRobolectric+powermockのを利用しています。あくまで検証用なので

Sample.java

Powermockでmockする為にわざとstaticメソッドななんかをでっち上げる

package kinjouj.app;

import android.util.Base64;

public class Sample {
    public static String encodeB64(String str) {
        return Base64.encodeToString(
            str.getBytes(),
            Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING
        );
    }
}

上記に書いた以前の検証コードのまま

MainActivity.java

上記のSampleを使って画面に出すだけなActivity UIを作る

package kinjouj.app;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {

    TextView textView;

    @Override
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        buildText();
    }

    void buildText() {
        if (textView == null) {
            textView = new TextView(this);
        }

        textView.setText(Sample.encodeB64("hoge"));
    }
}

これも前回のまま。でSample.encodeB64メソッドをmockStaticしてごにょごにょした結果のtextViewからのテキストを検証するのをRobolectricを使って行う

root_project/build.gradle

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

多分、Android Studioを使ってプロジェクトを生成したままのコード

root_project/app/build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    buildToolsVersion "25.0.0"
    defaultConfig {
        applicationId "app.kinjouj.myapplication"
        minSdkVersion 26
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
    testCompile "org.robolectric:robolectric:3.4.2"
    testCompile 'org.powermock:powermock-core:1.7.3'
    testCompile 'org.powermock:powermock-api-mockito2:1.7.3'
    testCompile 'org.powermock:powermock-module-junit4:1.7.3'
    testCompile 'org.powermock:powermock-module-junit4-rule:1.7.3'
    testCompile 'org.powermock:powermock-classloading-base:1.7.3'
    testCompile 'org.powermock:powermock-classloading-xstream:1.7.3'
}

※注意 powermock-api-mockitoではなくpowermock-api-mockito2です

これでテストできる環境が整ったんでテスト書く

SampleTestCase.java

package kinjouj.app;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.rule.PowerMockRule;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;

import android.util.Base64;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import static org.powermock.api.mockito.PowerMockito.*;

@RunWith(RobolectricTestRunner.class)
@PrepareForTest(Sample.class)
@PowerMockIgnore({
    "org.robolectric.*",
    "android.*",
    "javax.xml.parsers.*", // これ入れないといけない模様。一個下も
    "com.sun.org.apache.xerces.internal.jaxp.*"
})
public class SampleTestCase {

    @Rule
    public PowerMockRule rule = new PowerMockRule();

    @Test
    public void test1() {
        mockStatic(Sample.class);
        when(Sample.encodeB64(Mockito.anyString()))
            .thenAnswer(new Answer<String>() {
                @Override
                public String answer(InvocationOnMock invocation) throws Throwable {
                    // 実メソッドをコールしてみてテストしてみる
                    assertThat(
                        (String)invocation.callRealMethod(),
                        is(
                            Base64.encodeToString(
                                "hoge".getBytes(),
                                Base64.NO_WRAP | Base64.NO_PADDING
                            )
                        )
                    );

                    // コールされると返されるのは"ABC"

                    return "ABC";
                }
            });

        MainActivity activity = Robolectric.buildActivity(MainActivity.class)
            .create()
            .get();

        assertThat(activity.textView.getText().toString(), is("hoge"));
        verifyStatic();
    }
}

まぁテストを実行してレポートを見ると

まぁMainActivity#buildTextで指定してる「Sample.encodeB64("hoge")」としているのにもかかわらずmockしたメソッドで"ABC"しか返さないようになってるので

まぁめんどくさいのでアプリを作って実際にケースとして紹介するというところまでの気力は無かったので(ry

以上。とりあえずは現時点でのRobolectric3.4.2+powermock1.7.3を使ってテストするケースは終わり

追記: @RunWith(PowerMockRunner.class)を維持したままRobolectricを使う場合

PowerMockRunnerDelegateを使えばできるっぽい。てか公式に書いてた

package kinjouj.app;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.modules.junit4.PowerMockRunnerDelegate;
import org.powermock.modules.junit4.rule.PowerMockRule;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;

import android.util.Base64;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import static org.powermock.api.mockito.PowerMockito.*;

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(RobolectricTestRunner.class)
@PrepareForTest(Sample.class)
@PowerMockIgnore({
    "org.robolectric.*",
    "android.*",
    "javax.xml.parsers.*",
    "com.sun.org.apache.xerces.internal.jaxp.*"
})
public class SampleTestCase {

    @Rule
    public PowerMockRule rule = new PowerMockRule();

    // 以下省略

@PowerMockIgnoreの設定に関して 今時のMockitoのfinal classの扱いについて