読者です 読者をやめる 読者になる 読者になる

メモ2ブログ

メモtoウェブログ。旧ブログはこちら。 http://sakebook.blogspot.jp/

Robolectricを使ったライブラリプロジェクトのビルドでテストが失敗する

android robolectric

ライブラリプロジェクトの時だけ失敗する。

プロジェクトルートと、モジュールルートのbuild.gradleは次のようになっています。

  • build.gradle
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.12.+'
        classpath 'org.robolectric:robolectric-gradle-plugin:0.11.+'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}
  • ${module}/build.gradle
apply plugin: 'com.android.library'
apply plugin: 'robolectric'

android {
    compileSdkVersion 16
    buildToolsVersion "19.1.0"

    defaultConfig {
        applicationId "com.sakebook.android.dialoghelper"
        minSdkVersion 8
        targetSdkVersion 16
    }

    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }

    sourceSets {
        androidTest {
            setRoot('src/test')
        }
    }
}

robolectric {
    include "**/*Test.class"
    exclude "**/espresso/*.class"
}

dependencies {
    compile 'com.android.support:support-v4:19.1+'

    androidTestCompile 'junit:junit:4.11'
    androidTestCompile 'com.squareup:fest-android:1.0.8@aar'
    androidTestCompile('org.robolectric:robolectric:2.3')
}

apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle'

適当なテストコードを書いて、./gradlew clean testタスクを実行すると、下記のようなエラーがでます。

java.lang.RuntimeException: java.lang.NullPointerException
    at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:240)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:177)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:86)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:49)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:69)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:48)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.messaging.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
    at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
    at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:105)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.messaging.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:355)
    at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:64)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
    at java.lang.Thread.run(Thread.java:695)
Caused by: java.lang.NullPointerException
    at org.robolectric.AndroidManifest.initMetaData(AndroidManifest.java:364)
    at org.robolectric.res.builder.RobolectricPackageManager.addManifest(RobolectricPackageManager.java:364)
    at org.robolectric.internal.ParallelUniverse.setUpApplicationState(ParallelUniverse.java:77)
    at org.robolectric.RobolectricTestRunner.setUpApplicationState(RobolectricTestRunner.java:430)
    at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:236)
    ... 35 more

AndroidManifest.class周りでエラーを吐いてるようなので、
apply plugin: 'com.android.library'
apply plugin: 'android'
とそれぞれ実行したときのAndroidManifest.xmlを比べてみました。
ビルド時のAndroidManifest.xmlは以下のようになっています。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sakebook.android.dialoghelper"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="16" />

</manifest>

apply plugin: 'com.android.library' の場合

  • ${module}/build/intermediates/bundles/に出力されます。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sakebook.android.dialoghelper"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="16" />

</manifest>

apply plugin: 'android' の場合

  • ${module}/build/intermediates/manifests/debug/に出力されます。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sakebook.android.dialoghelper"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="16" />

    <application />

</manifest>

<application />が追加される

  • 本来は必須なのですが、ライブラリプロジェクトの場合は、親プロジェクトにAndroidManifest.xmlが必ずあるため、無くても動きます。
    しかし、親プロジェクトの場合は必須なので、pluginで自動で挿入してくれるようです。

  • これが無いと、Robolectricでエラーを吐くため、ライブラリプロジェクトに追加してあげましょう。

変更後のAndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sakebook.android.dialoghelper"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="16" />

    <application />

</manifest>

apply plugin: 'android'の場合と同じにしただけですが、これで無事エラーを起こさないため、テストができます。

まとめ

  • ライブラリプロジェクトでもちゃんと<application />を書きましょう。