はじめに
皆さん、ごきげんよう!れぶです!
今回の記事では、SharedPreferencesの代替策であるJetpack DataStore
に関して、基本的な単体テストの手順を整理します。
Preference DataStore(以降、DataStore)を用いた値の読み書きを単体テストレベルで検証したい方に特に参考になればと思います。それでは、参りましょう!!
開発環境
- MacBook Air
- Android Studio Dolphin | 2021.3.1
- Kotlin
- compileSdkVersion 33
- targetSdkVersion 33
- minSdkVersion 21
- Jetpack DataStore 1.1.0-alpha01
前提
Boolean値を読み書きするDataStoreを対象とします。saveLaunch()
の呼び出し前後で、保存された値が正しく変更できているかをテストします。
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = "preferences"
)
//DataStoreの設定
class LaunchDataStore(private val dataStore: DataStore<Preferences>) {
suspend fun saveLaunch() {
this.dataStore.edit {
it[PreferenceKeys.LAUNCH] = true
}
}
val preferenceFlow: Flow<Boolean> =
dataStore.data
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}.map {
it[PreferenceKeys.LAUNCH] ?: false
}
}
private object PreferenceKeys {
val LAUNCH = booleanPreferencesKey("isLaunch")
}
テスト手順
1. 依存関係の追加
//インストゥルメント化単体テスト用
androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test:rules:1.1.0'
//コルーチンの単体テスト用
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4"
適宜最新バージョンに合わせてください。
2. テストクラスの作成
今回はエミュレータで実行する単体テストであるため、src/androidTest/java
内にテストクラスを作成します。以降の手順に関しては、このクラスに記述します。
src/test/java
内で作成したクラスでテストしても上手く実行できないため、ご注意ください。
3. DataStoreインスタンスの生成
まずPreferenceDataStoreFactory.create()
でDataStoreのインスタンスを生成します。その際に必要な引数は、事前に定義しておきます。
今回はLaunchDataStoreクラス内でDataStoreの読み書きを記述しているため、LaunchDataStore
インスタンスも生成します。
private val TEST_DATASTORE_NAME = "preferences"
private val dispatcher = UnconfinedTestDispatcher()
private val context: Context =
InstrumentationRegistry.getInstrumentation().targetContext
private val scope = TestScope(dispatcher + Job())
private val dataStore: DataStore<Preferences> =
PreferenceDataStoreFactory.create(
scope = scope,
produceFile = { context.preferencesDataStoreFile(TEST_DATASTORE_NAME) }
)
private val launchDataStore = LaunchDataStore(dataStore)
4. テスト前処理の設定
DataStoreの読み書きはコルーチンを元に非同期で処理されるため、テスト前にDispatchers.setMain
を用いてMainDispatcher
をTestDispatcher
に置き換える必要があります。
また、念のためDataStoreで保存されるデータをクリアにするため、DataStoreファイル全体を削除します。複数回テストを繰り返した場合に、There are multiple DataStores active for the same file.
と怒られないように対策しています。
@Before
fun setup() {
//データを毎回新規にするためにファイル全削除
context.run {
File(filesDir, "datastore").deleteRecursively()
}
Dispatchers.setMain(dispatcher)
}
5. テスト後処理の設定
テスト終了後は、DispatcherとTestScopeをきちんとリセットしてあげましょう。
@After
fun tearDown() {
Dispatchers.resetMain()
scope.cancel()
}
6. 書き込み前後のデータをテスト
さていよいよテスト本番です。非同期処理のため、runTest
で囲みます。
デフォルトではfalse
が返ってきて、launchDataStore.saveLaunch()
の実行後はtrue
が返ってくればテスト大成功です。
@Test
fun validateIsTrue() {
scope.runTest {
assertEquals(false,launchDataStore.preferenceFlow.firstOrNull())
//trueに変更
launchDataStore.saveLaunch()
assertEquals(true,launchDataStore.preferenceFlow.firstOrNull())
}
}
全コード
おわりに
今回はJetpack Preference DataStoreの単体テスト方法をサクッとまとめました。DataStoreはコルーチンを使用しているため、コルーチンを用いたテスト方法(「参考サイト」に記載)が理解できていれば簡単に行えるのかなと実際やってみて感じました。
上記のやり方を応用してより複雑なDataStoreの単体テストも実現できると思うので、この記事が少しでも役立つと嬉しいです。以上です。ありがとうございました!
参考サイト
- DataStore and testing
- DataStoreのユニットテスト
- There are multiple DataStores active for the same file in HiltAndroidTest
- Build instrumented tests
- Testing Kotlin coroutines on Android
- DroidKaigi 2022 - Androidのモダンな技術選択にあわせて自動テストもアップデートしよう | Nozomi Takuma [JA]
- Kotlin coroutines 1.6.0-RCでリワークされたkotlinx-coroutines-testを見てみる