Robolectric (v4.3)の導入で迷える子羊を俺が救う[ Android / testing ]

![Robolectric](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261080/3dc62eaf-92f9-0660-58fd-9990c4e4da03.png)

## 記事の初版からだいたい一年が経ちました

（特にセットアップ関係は）ここを見たほうがよいと思います。
Android Developers > ドキュメント > ガイド > 「ローカル単体テストを作成する」
https://developer.android.com/training/testing/unit-testing/local-unit-tests?hl=ja
依存関係系はページの先頭に書いてあって、実際のコードは[フレームワークの依存関係を含める](https://developer.android.com/training/testing/unit-testing/local-unit-tests?hl=ja#include-framework-dependencies)あたりです。

`java.lang.IllegalStateException: No instrumentation registered!`で怒られたらAndroid Gradle Pluginを見直してみてくださいね。
https://stackoverflow.com/questions/53595837/androidx-no-instrumentation-registered-must-run-under-a-registering-instrumen

よいテストライフを👋

## これ is 何

Androidのテスト環境の[Robolectric](http://robolectric.org/) v4.3 の導入を紹介する記事です。

Robolectric v4.0以降大幅に書き方が変化したもののそれに対応していない記事が多く、
今新規に導入をしようとした場合には半端なレベルの日本語作業者は全員地獄を見ると思ったので、
次なる犠牲者を出さないためにも記事にすることにしました。~~ちなみに僕は地獄を見ました。~~

## 手順

### ビルド設定関連

書いてあるところ : [Robolectric GETTING-STARTED](http://robolectric.org/getting-started/)

```module-name/build.gradle
android {
  // ほかにも書いてある
    sourceSets {
      // ↓無ければ追記
        test.java.srcDirs += 'src/test/java'
        androidTest.java.srcDirs += 'src/androidTest/java'
    }

    testOptions {
        unitTests {
            includeAndroidResources = true
            returnDefaultValues = true
        }
    }
}

dependencies{
    // ほかにも書いてある
    testImplementation 'androidx.test:core:1.2.0'
    testImplementation 'androidx.test:runner:1.2.0'
    testImplementation 'androidx.test:rules:1.2.0'
    testImplementation 'androidx.test.ext:junit:1.1.1'
    testImplementation 'androidx.test.ext:truth:1.2.0'
    testImplementation 'com.google.truth:truth:0.42'

    testImplementation 'junit:junit:4.12'
    testImplementation 'org.robolectric:robolectric:4.3'
}
```


```gradle.properties
  # androidStudio 3.3以上では不要
  android.enableUnitTestBinaryResources=true
```

ここまでで環境の整備は完了です。かんたん！

## テストの記述

手作業でも AndroidStudio の`Create Test`でもよいので、テストのファイルを用意します。（`test.java.srcDirs`のほうのディレクトリです！）
ここで色々書いていくわけですが、[公式](http://robolectric.org/androidx_test/)を参考にするだけでは書ききれなかったりと、ハマりポイントでした。

### テストランナー

```kotlin
@RunWith(AndroidJUnit4::class)
```

v4.0以降を使うのであれば、たぶんandroidxを使わない手はないと思うのでAndroidJUnit4です。
Robolectricのものと自動で切り替わります。

### Contextの取得
```kotlin
ApplicationProvider.getApplicationContext()
```

InstrumentationRegistryは使用しません。

### ActivityScenarioの取得
```kotlin
val scenario = launch<CountActivity>(startActivityIntent)
```

### 例

実際の実装からいくらか切り抜いて例としているので半端になってしまうのですが、
こんな感じ↓のダイアログのイメージです。まさかの文字列表現。
 
> タイトル文字列 　　
> ［－］［＋］［ 5］

各ViewのIDは`titleText`,`buttonMinus`,`buttonPlus`,`count`。

機能は

- 数値をカウントアップ・ダウンする
- Intent経由で以下の値を渡せる
    - タイトル文字列 `IntentKey.TITLE_TEXT` (String)
    - 現在値 `IntentKey.COUNT` (Int)
    - 最小値 `IntentKey.LOWEST_COUNT` (Int)


```kotlin
@RunWith(AndroidJUnit4::class)
@Config(sdk = [Build.VERSION_CODES.O]) // 動作対象バージョンの指定ができます
class CountActivityTest {
    @Test fun onClickCount() {
        val startActivityIntent = Intent(ApplicationProvider.getApplicationContext(), CountActivity::class.java).also{
            it.putExtra(CountActivity.IntentKey.TITLE_TEXT.name,"TEST_TITLE")
            it.putExtra(CountActivity.IntentKey.COUNT.name, 3)
            it.putExtra(CountActivity.IntentKey.LOWEST_COUNT.name, 0)
        }

        val scenario = launch<CountActivity>(startActivityIntent)
        scenario.moveToState(Lifecycle.State.CREATED) // Activityの状態をCreatedにする＝onCreate()後の状態
        scenario.onActivity {
            assertNotEquals(it.titleText.text, "ERROR_TITLE")
            assertEquals(it.titleText.text, "TEST_TITLE")

            assertEquals(it.count.text, "3")
            assertNotEquals(it.count.text, "2")
            it.buttonMinus.performClick()
            assertEquals(it.count.text, "2")
            it.buttonMinus.performClick()
            it.buttonMinus.performClick()
            assertEquals(it.count.text, "0")
            it.buttonMinus.performClick()
            assertEquals(it.count.text, "0")

            it.buttonPlus.performClick()
            assertEquals(it.count.text, "1")
        }
    }
}
```

JVM上でActivity関連のテストができました。

## 既知の不具合（踏んだもの）

- [java 9 warnings (illegal reflective access operation) #4776](https://github.com/robolectric/robolectric/issues/4776)
  - 怒られます。（怒られるだけ）

- [Fix remaining usages of ByteBuffer.position(int) #5075](https://github.com/robolectric/robolectric/pull/5075) 
  - 自分が対象としたプロジェクトでは、Applicationクラスを継承したクラスの初期化処理中でTimes.javaが例外を吐きました。
  - Robolectric4.4では修正されています。
  - このエラーかな・・・？と思ったときには[githubページのトップ最下段にある](https://github.com/robolectric/robolectric)、  
   SNAPSHOTバージョンの利用を試してみるとよいかもしれません。  
   それで直ったら、近々リリースされるであろう次バージョンを待ちましょう。~~4.3の導入記事じゃないのかよ~~

## おわりに

なかなかv4.4のリリースありませんね・・・（2020.04.23）

happy testing !