Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hilt viewmodel injection into instrumentation tests

I was searching quite a lot for how to inject ViewModel into tests so I can test it. Lets say the viewmodel have a constructor injection with some business logic interactor. I can inject it into fragments easily but no success in tests.

@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())
    }

I tried to implement byViewModels() in test class and could inject viewmodel without constructor arguments but no success with them.

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

Reason: I want to be able to fully test my screen logic since the viewModel will handle the dependencies on interactors etc. there might be quite a lot of logic behind with various data flowing around. Testing the fragment would be most likely possible but way slower in a larger poject with a lot of tests.

I already read the https://developer.android.com/jetpack/guide#test-components , which is suggesting doing JUnit tests and mocking the dependencies in viewModel but then you have to create tests for each dependency separatelly and cant really test the logic for the whole screen

like image 619
Alex Avatar asked Aug 23 '20 07:08

Alex


People also ask

How do I use hilt in instrumentation testing?

Add the Hilt dependencies for the instrumentation test. Create an @AndroidEntryPoint activity to use as the container for the Fragment under test. Implement a utility class to launch the Fragment under test into the Hilt-enabled Activity.

What is a hilt ViewModel?

A Hilt View Model is a Jetpack ViewModel that is constructor injected by Hilt. To enable injection of a ViewModel by Hilt use the @HiltViewModel annotation: @HiltViewModel public final class FooViewModel extends ViewModel { @Inject FooViewModel(SavedStateHandle handle, Foo foo) { // ... } }

How do I use hiltandroidtest with hilt?

You must annotate any UI test that uses Hilt with @HiltAndroidTest. This annotation is responsible for generating the Hilt components for each test. Also, you need to add the HiltAndroidRule to the test class. It manages the components' state and is used to perform injection on your test: // UI tests here.

What is hilttestapplication_application?

You must set the generated Hilt test application to run in your instrumented tests or Robolectric tests as described in Test application. Note: Because HiltTestApplication_Application is code that Hilt generates at runtime, the IDE might highlight it in red until you run your tests.


1 Answers

The @HiltViewModel annotation, generates binding Modules that you would have otherwise written.

One of those being a module called BindsModule. This class is declared inside of a wrapper class that contains that multi-binding module as well as one for the key.

For example, let's say you have created a ViewModel called MyViewModel

package com.mypackage

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

Then the generated module would look something like this:

@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";
    }
  }
}

Therefore your ViewModel can replace that @Binds contract by simply using the @BindValue annotation on a property in your test class that matches the implementation type, in this case, it would be MyViewModel.

No need to uninstall any modules related to the 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()
    }
}
like image 72
mvbrenes Avatar answered Sep 24 '22 16:09

mvbrenes