使用 @HiltViewModel 进行 Jetpack Compose 仪器测试

Vah*_*hid 15 android kotlin android-jetpack-compose dagger-hilt

所以我想测试我的 jetpack compose 项目。在 Android 开发站点上按照 [这些说明] 1运行仪器测试非常容易,但是当您将@HiltViewModel注入添加到组合中时,事情就会变得复杂。

我正在尝试使用具有 @Inject 构造函数的 ViewModel 测试一个非常简单的撰写屏幕。屏幕本身看起来像这样:

@Composable
fun LandingScreen() {
    val loginViewModel: LoginViewModel = viewModel()

    MyTheme {
        Surface(color = MaterialTheme.colors.background) {
            val user by loginViewModel.user.observeAsState()
            if (user != null) {
                MainScreen()
            } else {
                LoginScreen(loginViewModel)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是视图模型:

@HiltViewModel
class LoginViewModel @Inject constructor(private val userService: UserService) : ViewModel() {
    val user = userService.loggedInUser.asLiveData()
}
Run Code Online (Sandbox Code Playgroud)

用户服务当然是由房间数据库支持的,loggedInUser 属性返回一个Flow.

事情在标准运行中按预期工作,但是当尝试在仪器测试中运行它时,它无法注入视图模型。

@HiltAndroidTest
class LandingScreenTest {
    @get:Rule
    var hiltRule = HiltAndroidRule(this)

    @get:Rule
    val composeTestRule = createComposeRule()

    @Inject
    lateinit var loginViewModel: LoginViewModel

    @Before
    fun init() {
        hiltRule.inject()
    }

    @Test
    fun MyTest() {
        composeTestRule.setContent {
            MyTheme {
                LandingScreen()
            }
        }

        composeTestRule.onNodeWithText("Welcome").assertIsDisplayed()
    }
}
Run Code Online (Sandbox Code Playgroud)

禁止注入 @HiltViewModel 类,因为它无法正确创建 ViewModel 实例。相反,通过 Android API(例如 ViewModelProvider)访问 ViewModel。注入的ViewModel:com.example.viewmodels.LoginViewModel

如何使用 ViewModelProvider 而不是 @HiltViewModel 来实现这一点?

小智 6

Hilt 需要一个入口点来注入字段。在这种情况下,这可能是一个用 @AndroidEntryPoint 注释的 Activity。您可以使用 MainActivity 来实现这一点,但这意味着您必须向每个测试添加代码才能导航到所需的屏幕,这可能会很乏味,具体取决于您的应用程序的大小,并且如果您的项目是多模块,则这是不可行的并且您当前的测试文件无权访问MainActivity。相反,您可以创建一个单独的虚拟 Activity,其唯一目的是托管您的可组合项(在本例中为 LoginScreen)并使用 @AndroidEntryPoint 对其进行注释。确保将其放入调试目录中,这样它就不会随项目一起提供。然后您可以使用 createAndroidComposeRule<Activity>() 来引用该可组合项。您不需要直接注入 ViewModel,因此也可以删除该行。

最后你的测试文件应该是这样的:

@HiltAndroidTest
class LandingScreenTest {
    @get:Rule(order = 0)
    val hiltRule = HiltAndroidRule(this)

    @get:Rule(order = 1)
    val composeRule = createAndroidComposeRule<LoginTestActivity>()

    @Before
    fun init() {
        hiltRule.inject()
    }

    @Test
    fun MyTest() {
        composeTestRule.onNodeWithText("Welcome").assertIsDisplayed()
    }
}
Run Code Online (Sandbox Code Playgroud)

你的虚拟活动可能如下所示:

@AndroidEntryPoint
class LoginTestActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) { 
        super.onCreate(savedInstanceState)
        setContent {
            LoginScreen()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

调试目录将如下所示:带有虚拟活动的调试目录

是的,调试目录有自己的清单,您应该在其中添加虚拟活动。设置导出为 false。


小智 2

尝试做这样的事情:

@HiltAndroidTest
class LandingScreenTest {
    @get:Rule
    var hiltRule = HiltAndroidRule(this)

    @get:Rule
    val composeTestRule = createComposeRule()

    // Remove this line @Inject
    lateinit var loginViewModel: LoginViewModel

    @Before
    fun init() {
        hiltRule.inject()
    }

    @Test
    fun MyTest() {
        composeTestRule.setContent {
            loginViewModel= hiltViewModel() // Add this line
            MyTheme {
                LandingScreen()
            }
        }

        composeTestRule.onNodeWithText("Welcome").assertIsDisplayed()
    }
}
Run Code Online (Sandbox Code Playgroud)