How to test AndroidViewModel in unit test

Viewed 1578

I'm trying to test ViewModel(AndroidViewModel parent), in unit tests(test folder) but when using

val application = ApplicationProvider.getApplicationContext<Application>()

it gives me this error

java.lang.IllegalStateException: No instrumentation registered! Must run under a registering instrumentation.

ExampleUnitTest

class ExampleUnitTest {
    private lateinit var myAndroidViewModel: MyAndroidViewModel
    private val app = ApplicationProvider.getApplicationContext<Application>()

    @Before
    fun init() {
        myAndroidViewModel = MyAndroidViewModel(app)
    }

    @Test
    fun addition_isCorrect() {
        assertEquals(4, 2 + 2)
    }
}

build.gradle

android {
    testOptions {
        unitTests.returnDefaultValues = true
    }
}
dependencies {
    testImplementation 'junit:junit:4.12'
    testImplementation 'androidx.test:core-ktx:1.3.0'
}

If I use robolectric, I no longer get said error, but I don't feel that it is a good option, since robolectric is more for ui test.

I hope you can help me. Thanks.

4 Answers

For Unit testing, one may want to use @ActivityScoped and then @Inject the constructor ...


But for testing ViewModel I'd rather use androidx.hilt:hilt-lifecycle-viewmodel:

dependencies {
  implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'

  // When using Kotlin.
  kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01'

  // When using Java.
  annotationProcessor 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
}

Then one can @ViewModelInject:

class ExampleViewModel @ViewModelInject constructor(
  private val repository: ExampleRepository,
  @Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
  ...
}

See Hilt and Jetpack integrations.


Here's another one example using Hilt with MockK, which it's author summarizes as:

Testing with Hilt, just @UninstallModules and then @BindValue.

You can use Robolectric in a Unit Test, inside test directory, and choose the platform Application class.

@RunWith(RobolectricTestRunner::class)
@Config(application = Application::class)
class ViewModelTest {

    @Test
    @Throws(Exception::class)
    fun someTest() {
        val application = RuntimeEnvironment.application
        val testViewModel = MyAndroidViewModel(application)
        testViewModel.foo()
    }

}

Or you can use an InstrumentationTest inside androidTest directory, and cast the InstrumentationRegistry.getTargetContext().applicationContext to Application:

@RunWith(AndroidJUnit4::class)
class ViewModelTest {

    @Test
    @Throws(Exception::class)
    fun someTest() {
        val application = ApplicationProvider.getApplicationContext() as Application
        val testViewModel = MyAndroidViewModel(application)
        testViewModel.foo()
    }

}

Hope it helped!

If you want to run the tests with the target application's Application context, you can create your model for testing with

val app = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application
val model = YourModel(app)
// now run tests with model

For doing unit tests, Mockito library is a must (now use MockK for Kotlin).

So, for unit testing AndroidViewModel, we can mock() the application object like this:

val application = Mockito.mock(Application::class.java)
val viewModel = FirstViewModel(application)
Related