@HiltAndroidApp
class MyApplication : Application()
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface(color = MaterialTheme.colors.background) {
MyApp()
}
}
}
}
}
@Composable
fun MyApp() {
val navController = rememberNavController()
NavHost(navController, startDestination = "Screen1") {
composable("Screen1") {
Screen1(hiltViewModel()) {
navController.navigate("Screen2/$it")
}
}
composable(
route = "Screen2/{value}",
arguments = listOf(
navArgument("value") { type = NavType.IntType },
)
) {
val arguments = requireNotNull(it.arguments)
val value = arguments.getInt("value")
Screen2(value)
}
}
}
@Composable
fun Screen1(
viewModel: Screen1ViewModel,
onClickItem: (Int) -> Unit,
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(viewModel.values) { value ->
Text(
text = value.toString(),
modifier = Modifier
.fillMaxWidth()
.clickable {
onClickItem(value)
}
.padding(16.dp)
)
}
}
}
@Composable
fun Screen2(value: Int) {
Text(
text = "value = $value",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
}
@HiltViewModel
class Screen1ViewModel @Inject constructor(
valueProvider: ValueProvider
) : ViewModel() {
val values: List<Int> = valueProvider.getValues()
}
interface ValueProvider {
fun getValues(): List<Int>
}
class DefaultValueProvider @Inject constructor() : ValueProvider {
override fun getValues(): List<Int> = (0 until 20).map { Random.nextInt() }
}
@Module
@InstallIn(SingletonComponent::class)
interface ValueProviderModule {
@Binds
@Singleton
fun bindValueProvider(provider: DefaultValueProvider): ValueProvider
}
MyApp composable では最初に Screen1が表示される。
Screen1 にはランダムな Int 値のリストが表示され、タップすると Screen2 に遷移し、タップした値が表示される。
Screen1 のリストをタップして Screen2 に遷移し、タップしたところの値が表示されていることをテストしたいとする。
Screen1 の引数の Screen1ViewModel は hiltViewModel() で取得しているので、テストでも Hilt が動くようにしたい。
ただし、Screen1ViewModel で使っている ValueProvidier はテスト用のものに差し替えたい。
1. テスト用のライブラリを追加する
参考 (Hilt) : https://developer.android.com/training/dependency-injection/hilt-testing#testing-dependencies
参考 (Compose) : https://developer.android.com/jetpack/compose/testing#setup
dependencies {
...
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_version"
}
2. テスト時に HiltTestApplication が使われるように CustomTestRunner を用意する
参考 : https://developer.android.com/training/dependency-injection/hilt-testing#instrumented-tests
(継承元のApplicationが必要な場合は https://developer.android.com/training/dependency-injection/hilt-testing#custom-application に方法が書いてある)
app/src/androidTest
class CustomTestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}
build.gradle (app)
android {
...
defaultConfig {
...
testInstrumentationRunner "com.sample.myapplication.CustomTestRunner"
}
...
}
この設定をしないと
java.lang.IllegalStateException: Hilt test, com.sample.myapplication.MyAppTest, cannot use a @HiltAndroidApp application but found com.sample.myapplication.MyApplication. To fix, configure the test to use HiltTestApplication or a custom Hilt test application generated with @CustomTestApplication.
のようなエラーがでる
3. @AndroidEntryPoint がついたテスト用の Activity を debug に用意する
app/src/debug
@AndroidEntryPoint
class HiltTestActivity : ComponentActivity()
app/src/debug/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sample.myapplication">
<application>
<activity
android:name=".HiltTestActivity"
android:exported="false" />
</application>
</manifest>
4. テストを書く
テストクラスに @HiltAndroidTest をつける
HiltAndroidRule が先になるように oder を指定する。
参考 : https://developer.android.com/training/dependency-injection/hilt-testing#multiple-testrules
createAndroidComposeRule() に 3. で作った HiltTestActivity を指定する。createComposeRule() だと
Given component holder class androidx.activity.ComponentActivity does not implement interface dagger.hilt.internal.GeneratedComponent or interface dagger.hilt.internal.GeneratedComponentManager
のようなエラーがでる
binding の差し替えは https://developer.android.com/training/dependency-injection/hilt-testing#testing-features に書いてある。
@UninstallModules(ValueProviderModule::class)
@HiltAndroidTest
class MyAppTest {
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule<HiltTestActivity>()
@BindValue
val repository: ValueProvider = object : ValueProvider {
override fun getValues(): List<Int> {
return listOf(100)
}
}
@Test
fun test() {
composeTestRule.setContent {
MaterialTheme {
Surface(color = MaterialTheme.colors.background) {
MyApp()
}
}
}
// Screen1 に 100 が表示されている
composeTestRule.onNodeWithText("100").assertIsDisplayed()
// Screen1 の 100 が表示されている Node をクリック
composeTestRule.onNodeWithText("100").performClick()
// Screen2 に value = 100 が表示されている
composeTestRule.onNodeWithText("value = 100").assertIsDisplayed()
}
}
Kotlinよくわかんない ボタンを押して画面が変わればいいのに基盤が大げさになりすぎたかも 悲しみ http://rb.tabirepo.online/archives/1020
返信削除なぜ今“6G”か?2030年を見据えた富士通の覚悟
返信削除http://www.jflabo.sakura.ne.jp/2021/
返信削除こんな感じのシステムです。アンドロイドアプリの
メソッド名とイベントが変わったみたいで
なかなか作れません 悩ましいです。
自動車やバス 車両 車載機にも応用されていくのかなぁ