5

我一直在寻找如何将 ViewModel 注入测试以便进行测试。假设 viewmodel 有一个带有一些业务逻辑交互器的构造函数注入。我可以很容易地将它注入到片段中,但在测试中没有成功。

@HiltAndroidTest
class ViewModelTest

 val randomViewmodel: RandomViewmodel// now what ? since by viewModels() is not accessible in tests

    @Test
    fun viewModelTet() {
        randomViewmodel.triggerAction()
        assertEquals(RandomVIewState(1), randomViewmodel.getState())
    }

我试图在测试类中实现 byViewModels() 并且可以在没有构造函数参数的情况下注入 viewmodel 但没有成功。

class RandomViewmodel @ViewModelInject constructor(
     private val randomInteractor: RandomInteractor
) : ViewModel
Caused by: java.lang.InstantiationException: class app.RandomViewModel has no zero argument constructor

原因:我希望能够完全测试我的屏幕逻辑,因为 viewModel 将处理对交互器等的依赖关系。随着各种数据的流动,背后可能有很多逻辑。测试片段很可能是可能的,但在具有大量测试的大型项目中速度较慢。

我已经阅读了https://developer.android.com/jetpack/guide#test-components,它建议在 viewModel 中进行 JUnit 测试和模拟依赖项,但是您必须分别为每个依赖项创建测试并且不能真正测试整个屏幕的逻辑

4

1 回答 1

6

@HiltViewModel注释会生成您本来会编写的绑定模块。

其中一个是名为BindsModule的模块。此类在包含该多绑定模块以及一个用于键的包装类中声明。

例如,假设您创建了一个名为MyViewModel

package com.mypackage

@HiltViewModel
class MyViewModel @Inject constructor(
    private val someDependency: MyType
) : ViewModel()

然后生成的模块将如下所示:

@OriginatingElement(
    topLevelClass = MyViewModel.class
)
public final class MyViewModel_HiltModules {
  private MyViewModel_HiltModules() {
  }

  @Module
  @InstallIn(ViewModelComponent.class)
  public abstract static class BindsModule {
    private BindsModule() {
    }

    @Binds
    @IntoMap
    @StringKey("com.mypackage.MyViewModel")
    @HiltViewModelMap
    public abstract ViewModel binds(MyViewModel vm);
  }

  @Module
  @InstallIn(ActivityRetainedComponent.class)
  public static final class KeyModule {
    private KeyModule() {
    }

    @Provides
    @IntoSet
    @HiltViewModelMap.KeySet
    public static String provide() {
      return "com.mypackage.MyViewModel";
    }
  }
}

因此,您的 ViewModel 可以通过简单地使用与实现类型匹配的测试类中的属性上的注释来替换该@Binds合同@BindValue,在这种情况下,它将是MyViewModel.

无需卸载与 ViewModel 相关的任何模块。

@HiltAndroidTest
class MyFragmentInstrumentedUnitTest {
    @get:Rule val hiltRule = HiltAndroidRule(this)

    // either a subclass or a mock, as long as the types match
    // it will provide this instance as the implementation of the abstract binding 
    // `public abstract ViewModel binds(MyViewModel vm);`
    @BindValue
    val mockMyViewModel= mock<MyViewModel>()

    @Before
    fun init() {
        hiltRule.inject()
    }
}
于 2021-04-30T16:34:11.540 回答