Google Play Serviceが入ったアプリを Robolectric (正確には Robospock) でテストしようとしたらハマったのでメモ。
追加: 偉そうに長々書きましたが、正しくは RobolectricGradleTestRunner
や GradleRoboSputnik
を使うのがいいのかもしれません。でも一々 @RunWith
で指定するのは面倒そうな気もする。
問題1:正しい AndroidManifestを見つけてくれない
GooglePlayServiceを使うには AndroidManifestに metaタグで versionを仕込まなくてはいけません。でも、正しい AndroidManifestを見てくれないと、それが定義されてないのでテストの初期化で以下のように言って落ちます:
java.lang.RuntimeException: java.lang.IllegalStateException: A required meta-data tag in your app's AndroidManifest.xml does not exist. You must have the following declaration within the <application> element:
問題2: @integer/google_play_services_version
を見つけてくれない
単純に @Config
とかで src/main/AndroidManifest.xml
を指定すると今度は次のようなエラーが出ます。
Caused by: java.lang.NullPointerException
at org.robolectric.manifest.MetaData.init(MetaData.java:55)
at org.robolectric.manifest.AndroidManifest.initMetaData(AndroidManifest.java:343)
これは、google_play_services_version
が src/main/res
以下ではなく、Google Play service の aarで定義されてるためです。そのため、参照を解決できなくて NPEになるようです。
解法: Gradleが処理してくれた manifest, resourceを使う
これらの問題を解決するには実際の Android端末で起こることの真似をして、Robolectricが APKに含まれるのと同じファイルを参照するようにすればいいのです。具体的には manifest, resource, assetのディレクトリを正しく指定してやればいいのです。
また、こうすればそれ以外の問題 (flavorごとにレイアウトの違いもテストしたいとか) も解消できて一挙両得です。
しかしそれらのファイルが生成されるディレクトリは buildTypeや flavorで違うので @Config
でソース中に指定するわけにも行きません。というわけで、お馴染みの gradle hackで乗り切ります。
testOptions {
unitTests {
all {
// Google Play Service requires meta tag in manifest
// and merged resource referred by it.
def words = it.name.split("(?=[A-Z])")*.toLowerCase()
def variantDir = "${words[1]}/${words[2]}"
def dir='build/intermediates'
def manifest="${dir}/manifests/full/${variantDir}/AndroidManifest.xml"
def resourceDir="${dir}/res/merged/${variantDir}"
def assetDir="${dir}/assets/${variantDir}"
jvmArgs "-Dandroid.manifest=${manifest}"
jvmArgs "-Dandroid.resources=${resourceDir}"
jvmArgs "-Dandroid.assets=${assetDir}"
}
}
}
これで無事ただしい Manifestやらを読んでくれるようになりました。
ちなみに、build variantごとに applicationIdを変えてる場合、やや面倒なことになるというのを聞きましたが、よくわかっておりません。
注意書き
ちなみに自分は Robospockで試しました。多分素の Robolectricでも使えると思いますが、試してませんので各自注意してお使いください。
また、書いていて気が付きましたが、上の例は flavor と buildTypeを組み合わせている例です。flavorを使っていないとちょこっと書き換えないと行けないとは思います。すみません。