ViewModel Unit test - android

I have a viewmodel that only emits the value of repo when I subscribe to it in the activity. I am trying to unit test the viewmodel (see code below) but I am getting NPE because repo is null. How can I unit test it? Is it possible?
class MainViewModel #ViewModelInject constructor(mainRepository: MainRepository) : ViewModel() {
val repo: LiveData<Resource<List<Repository>>> = mainRepository.getRepositories()
}
#RunWith(JUnit4::class)
class MainViewModelTest {
#Rule
#JvmField
val instantTaskExecutorRule = InstantTaskExecutorRule()
private val mainRepository = mock(MainRepository::class.java)
private lateinit var mainViewModel: MainViewModel
#Before
fun init() {
mainViewModel = MainViewModel(mainRepository)
}
#Test
fun testGetRepos() {
mainViewModel.repo.observeForever(mock()) /* NPE at this point as repo is null*/
verify(mainRepository).getRepositories()
}
}

Create an interface something like IMainRepository have your actual repository class implement it
class MainRepository : IMainRepository
Then change your ViewModel constructor to accept the interface
class MainViewModel #ViewModelInject constructor(mainRepository: IMainRepository) : ViewModel()
Then create a Mock class that implements the interface and what it is suppose to do
class MockMainRepository : IMainRepository
in your test create a new instance of the mock class and pass that to your ViewModel to test
private val mockMainRepository = MockMainRepository()
#Before
fun init() {
mainViewModel = MainViewModel(mainRepository)
}

Related

How to test getter, setter method of a LiveData Android?

I am using Hilt for DI in my project. I am trying write unit test cases for LiveData object, but it's not coming under coverage.
ViewModel
#HiltViewModel
class HealthDiagnosticsViewModel #Inject constructor(
private var networkHelper: NetworkHelper
) : ViewModel() {
var result = MutableLiveData<Int>()
.....
}
My unit test class is as below:
HealthViewModelTest
#HiltAndroidTest
#RunWith(RobolectricTestRunner::class)
#Config(application = HiltTestApplication::class)
class HealthDiagnosticsViewModelTest{
#get:Rule
var hiltRule = HiltAndroidRule(this)
#Inject
lateinit var networkHelper: NetworkHelper
lateinit var healthDiagnosticsViewModel: HealthDiagnosticsViewModel
#Before
fun setUp() {
hiltRule.inject()
healthDiagnosticsViewModel = HealthDiagnosticsViewModel(networkHelper)
}
#Test
fun testGetResult() {
val result = healthDiagnosticsViewModel.result.value
Assert.assertEquals(null, result)
}
#Test
fun testSetResult() {
healthDiagnosticsViewModel.result.value = 1
Assert.assertEquals(1, healthDiagnosticsViewModel.result.value)
}
}
Test Cases are passed but it's not coming under method coverage.
I'll share with you the an example of my code that would solve your problem.
I'm usnig ViewModel with Dagger Hilt
You don't have to use Robelectric, you can use MockK library.
Replace your HiltRule with this Rule:
#get:Rule
var rule: TestRule = InstantTaskExecutorRule()
This is my ViewModel class
using MockK, you can mock the networkHelper class without Hilt.
So, your setup method will be like that:
lateinit var networkHelper: NetworkHelper
......
......
......
#Before
fun setUp() {
networkHelper = mockk<NetworkHelper>()
healthDiagnosticsViewModel = HealthDiagnosticsViewModel(networkHelper)
}
4)The most important part in your test is to Observe to the LiveData first.
#Test
fun testGetResult() {
healthDiagnosticsViewModel.result.observeForever {}
val result = healthDiagnosticsViewModel.result.value
Assert.assertEquals(null, result)
}
You can observe to the livedata for each unit test, but keep in mind to Observe first before change data.

How to pass viewmodel to fragment using launchFragmentInHiltContainer

I was trying to write a test case for my fragment. The fragment kind of look like this,
#AndroidEntryPoint
class MainFragment : BaseFragment() {
val viewModel: HomeFragmentViewModel by viewModels()
......
}
Now in my test class,
#RunWith(AndroidJUnit4::class)
#LargeTest
#HiltAndroidTest
#ExperimentalCoroutinesApi
class MainFragmentTest {
#get:Rule
var hiltRule = HiltAndroidRule(this)
#Before
fun init() {
hiltRule.inject()
}
#Test
fun testFragmentDisplays() {
val fakeRepository = FakeRepository()
val scenerio = launchFragmentInHiltContainer<MainFragment>() {
//(this as MainFragment).viewModel = HomeFragmentViewModel(fakeRepository)
}
}
}
The problem is viewModel in the Fragment is val and it has to be val because I am using viewModels() to create the viewmodel. Is there anyway I can create the viewmodel and use in my fragment?
Thanks in Advance.
Make Your BaseFragment Like This
abstract class BaseFragment<VM: ViewModel>: Fragment() {
protected lateinit var viewModel: VM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(getViewModelClass())
.....
}
}
Then Extend it Like This
#AndroidEntryPoint
class MainFragment : BaseFragment<HomeFragmentViewModel>() {
......
}
And HomeFragmentViewModel Should be like this
#HiltViewModel
class HomeFragmentViewModel #Inject constructor(
private val YourRepository: YourRepository
) : ViewModel() {
........
}

Hilt - How to inject ViewModel interface?

Based on the Hilt tutorial, ViewModels needs to be inject the following way:
#HiltViewModel
class ExampleViewModel #Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val repository: ExampleRepository
) : ViewModel() {
...
}
However, in my case, I want to use an interface:
interface ExampleViewModel()
#HiltViewModel
class ExampleViewModelImp #Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val repository: ExampleRepository
) : ExampleViewModel, ViewModel() {
...
}
Then I want to inject it via the interface
#AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
private val exampleViewModel: ExampleViewModel by viewModels()
...
}
How to make this work?
viewModels requires child of ViewModel class
val viewModel: ExampleViewModel by viewModels<ExampleViewModelImp>()
Had a similar problem where I wanted to Inject the ViewModel via interface, primarily because to switch it with a fake implementation while testing. We are migrating from Dagger Android to Hilt, and we had UI tests that used fake view models. Adding my findings here so that it could help someone whose facing a similar problem.
Both by viewModels() and ViewModelProviders.of(...) expects a type that extends ViewModel(). So interface won't be possible, but we can still use an abstract class that extends ViewModel()
I don't think there is a way to use #HiltViewModel for this purpose, since there was no way to switch the implementation.
So instead, try to inject the ViewModelFactory in the Fragment. You can switch the factory during testing and thereby switch the ViewModel.
#AndroidEntryPoint
class ListFragment : Fragment() {
#ListFragmentQualifier
#Inject
lateinit var factory: AbstractSavedStateViewModelFactory
private val viewModel: ListViewModel by viewModels(
factoryProducer = { factory }
)
}
abstract class ListViewModel : ViewModel() {
abstract fun load()
abstract val title: LiveData<String>
}
class ListViewModelImpl(
private val savedStateHandle: SavedStateHandle
) : ListViewModel() {
override val title: MutableLiveData<String> = MutableLiveData()
override fun load() {
title.value = "Actual Implementation"
}
}
class ListViewModelFactory(
owner: SavedStateRegistryOwner,
args: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, args) {
override fun <T : ViewModel?> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle
): T {
return ListViewModelImpl(handle) as T
}
}
#Module
#InstallIn(FragmentComponent::class)
object ListDI {
#ListFragmentQualifier
#Provides
fun provideFactory(fragment: Fragment): AbstractSavedStateViewModelFactory {
return ListViewModelFactory(fragment, fragment.arguments)
}
}
#Qualifier
annotation class ListFragmentQualifier
Here, ListViewModel is the abstract class and ListViewModelImpl is the actual implementation. You can switch the ListDI module while testing using TestInstallIn. For more information on this, and a working project refer to this article
Found a solution using HiltViewModel as a proxy to the actual class I wish to inject. It is simple and works like a charm ;)
Module
#Module
#InstallIn(ViewModelComponent::class)
object MyClassModule{
#Provides
fun provideMyClas(): MyClass = MyClassImp()
}
class MyClassImp : MyClass {
// your magic goes here
}
Fragment
#HiltViewModel
class Proxy #Inject constructor(val ref: MyClass) : ViewModel()
#AndroidEntryPoint
class MyFragment : Fragment() {
private val myClass by lazy {
val viewModel by viewModels<Proxy>()
viewModel.ref
}
}
Now you got myClass of the type MyClass interface bounded to viewModels<Proxy>() lifeCycle
It's so simple to inject an interface, you pass an interface but the injection injects an Impl.
#InstallIn(ViewModelComponent::class)
#Module
class DIModule {
#Provides
fun providesRepository(): YourRepository = YourRepositoryImpl()
}

Cannot create an instance of class ViewModel using dagger hilt

My ViewModel:
class LoginViewModel #ViewModelInject constructor(
private val loginUseCase: LoginUseCase
) : ViewModel() {
val currentResult: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
fun loginUseCase(username: String, password: String) {
viewModelScope.launch {
loginUseCase.invoke(username, password).apiKey.let {
currentResult.value = it
}
}
}
}
Is being used by my MainActivity:
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val loginViewModel: LoginViewModel by viewModels()
And I know that the ViewModelProvider is expecting a empty constructor but I need to use the LoginUseCase:
class LoginUseCase #Inject constructor(
private val apiService: ApiServiceImpl
) : UseCase<Unit>() {
suspend operator fun invoke(username: String, password: String) =
apiService.login(username, password)
}
Inside the modelView, but i get the error:
Cannot create an instance of class com.example.myboards.ui.login.LoginViewModel
in runtime, and I dont know how I could manage the LoginUseCase inside the LoginViewModel
Provide a ViewModel by annotating it with #HiltViewModel and using the #Inject annotation in the ViewModel object's constructor.
#HiltViewModel
class LoginViewModel #Inject constructor(
private val loginUseCase: LoginUseCase
) : ViewModel() {
...
}
Hilt needs to know how to provide instances of ApiServiceImpl, too. Read here to know how to inject interface instances with #Binds.
Let me know If you still have a problem.

Unit test ViewModel class

I am trying to write unit test for my ViewModel class.
In this case, I have an activity that when created, it subscribes to my repo (LiveData from viewModel) and retrieves a list of Github repositories from network or database.
What should I be testing here? I tried to write two test methods:
dontFetchWithoutObservers which fails with the following:
[![enter image description here][1]][1]
fetchWhenObserved which fails with the following:
[![enter image description here][2]][2]
Here's my ViewModel class:
class MainViewModel #ViewModelInject constructor(mainRepository: MainRepository) : ViewModel() {
val repo: LiveData<Resource<List<Repository>>> = mainRepository.getRepositories()
}
And my ViewModel test class:
#RunWith(JUnit4::class)
class MainViewModelTest {
#Rule
#JvmField
val instantTaskExecutorRule = InstantTaskExecutorRule()
private val mainRepository = mock(MainRepository::class.java)
private lateinit var mainViewModel: MainViewModel
#Before
fun init() {
mainViewModel = MainViewModel(mainRepository)
}
#Test
fun dontFetchWithoutObservers() {
verify(mainRepository, never()).getRepositories()
}
#Test
fun fetchWhenObserved() {
mainViewModel.repo.observeForever(mock())
verify(mainRepository).getRepositories()
}
}
you are not mocking the getRepositories method and in fact the error you get is: NPE.
Provide a mock for getRepositories method.
`when`(mainRepository.getRepositories()).thenReturn(YourObject)
Also I would go and check the contents of getRepositories instead of just verifying that it is being invoked.

Categories

Resources