- @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)
- }
- }
- 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()
- <?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()
- }
- }
0 件のコメント:
コメントを投稿