🧪 Android Glance の単体テストの備忘録
Glance とは
Android のホーム画面に表示する Widget を Jetpack compose で作成できるライブラリ。
Glance 1.1.0 で安定版リリースされて、Google I/O 2025 でも触れられていたので、今後も開発が進められそう。
↓Google I/O 2025 の Widget の部分。
Glance 単体テスト
以前は UiState の単体テストを記述することでなんとかデグレチェックを担保していたが、どのような表示になっているかまでは難しかった。
Glance のテストツールを使うことで Jetpack compose の単体テストのように UI Automator を使用せずに記述できる。
環境
- Android Studio Android Studio Meerkat | 2024.3.1 Patch 2
- Kotlin 2.1.21
- Glance 1.1.1
- Robolectric 4.14.1
設定
公式サイトでは下記のような記述で設定できると書かれている。
今回は LocalContext
が必要で Robolectric も使用するので、差分を追加。
android {
...
+ testOptions {
+ unitTests {
+ // For Robolectric
+ isIncludeAndroidResources = true
+ }
+ }
}
dependencies {
// Other Glance and Compose runtime dependencies.
...
testImplementation("androidx.glance:glance-testing:1.1.1")
testImplementation("androidx.glance:glance-appwidget-testing:1.1.1")
testImplementation("org.robolectric:robolectric:4.14.1")
...
// You may include additional dependencies, such as Robolectric, if your test
// needs to set a LocalContext.
+ testImplementation("androidx.test.ext:junit-ktx:1.2.1")
}
テストコード
import androidx.test.core.app.ApplicationProvider
...
@RunWith(RobolectricTestRunner::class)
class WidgetTest {
private val context: Context = ApplicationProvider.getApplicationContext() // ①
@Test
fun itemEmpty_shouldShowLoadingIndicator() = runGlanceAppWidgetUnitTest {
provideComposable {
CompositionLocalProvider(
// NOTE: Scaffold の内部で LocalContext が使用されているので必要 ②
LocalContext provides context,
) {
WidgetComposable(
items = emptyList(),
onItemClick = {},
)
}
}
onNode(hasTestTag("loading_indicator")) // ③
.assertExists()
}
}
context
取得
① androidx.test.ext:junit-ktx
の androidx.test.core.app.ApplicationProvider
をインポートしてそこから context
を取得。
context
を使用するので @RunWith(RobolectricTestRunner::class)
か @RunWith(AndroidJUnit4::class)
などを忘れずに設定。
LocalContext
を上書き
② CompositionLocalProvider
を使って①で取得した context
に上書き。
これがないと LocalContext
を使用した Scaffold
などのコンポーネントなどがあると下記のようなエラーになる。
java.lang.IllegalStateException: No default context
LocalContext
を使用していない Scaffold
はいつリリースされるのか
余談:main ブランチの Scaffold は LocalContext
を使用していないけど、Glance 1.1.1 の Scaffold はまだ LocalContext
を使用している。使用していない版はいつリリースされるのだろうか。
3163676: [glance] Fix resource check for corner radius | https://android-review.googlesource.com/c/platform/frameworks/support/+/3163676
Submitted Jul 10, 2024
③ Glance のテストタグ
通常の Compose のテストタグと少し違う。
1. テストタグの設定方法
GlanceModifier
には通常の Compose にある Modifier#testTag
がないので sementics で設定する。
// 通常の Compose
Modifier.testTag(tag = "loading_indicator"),
// Glance
GlanceModifier.semantics { testTag = "loading_indicator" }
testTagsAsResourceId
が不要
2. ルートのコンポーネントで 当然と言えば当然なんだが、UI Automator を使用しているわけではないので、不要。
むしろ、設定するプロパティがないので、UI Automator を使用してテストすることができない?
Modifier.semantics { testTagsAsResourceId = true }
Jetpack Compose × UiAutomator の相互運用で詰まった時の備忘録はこっち。
文字列の取得
これは Glance に限らず、context
から取得することもできる。
@Test
fun isErrorTrue_shouldShowErrorMessage() = runGlanceAppWidgetUnitTest {
val errorMessage = context.getString(R.string.widget_error)
provideComposable {
CompositionLocalProvider(
// NOTE: Scaffold の内部で LocalContext が使用されているので必要
LocalContext provides context,
) {
WidgetComposable(
items = emptyList(),
isError = true,
onItemClick = {},
)
}
}
onNode(hasText(errorMessage))
.assertExists()
}
余談
文字列取得は通常の Compose のように下記のような util があると便利
@Composable
@ReadOnlyComposable
fun stringResource(@StringRes id: Int): String {
val resources = LocalContext.current.resources
return resources.getString(id)
}
@Composable
@ReadOnlyComposable
fun stringResource(@StringRes id: Int, vararg formatArgs: Any): String {
val resources = LocalContext.current.resources
return resources.getString(id, *formatArgs)
}
参考
StringResources.android.kt
Discussion