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.
Related
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)
}
I am new to testing and I am writing a test for my ViewModel. I have a couple of questions about it.
In my Test method, unless I add a delay(10), the test fails saying:
Wanted but not invoked:
breweryRepository.getBeerStyles();
-> at com.helenc.test.repositories.BreweryRepository.getBeerStyles(BreweryRepository.kt:12)
Actually, there were zero interactions with this mock.
Why do I need a delay? Is there a better way to do it?
I added the test in the test folder (not in androidTest). Is this correct? I am not sure since I am using some androidx dependencies, like androidx.lifecycle.Observer or androidx.arch.core.executor.testing.InstantTaskExecutorRule.
This is my ViewModel test file:
#RunWith(JUnit4::class)
class BeerStylesViewModelTest {
private lateinit var viewModel: BeerStylesViewModel
private lateinit var repository: BreweryRepository
private lateinit var beerStylesObserver: Observer<Resource<List<StylesData>>>
private val successResource = Resource.success(mockedBeerStylesList)
#Rule
#JvmField
val instantExecutorRule = InstantTaskExecutorRule()
#ObsoleteCoroutinesApi
private val mainThreadSurrogate = newSingleThreadContext("UI thread")
#ObsoleteCoroutinesApi
#ExperimentalCoroutinesApi
#Before
fun setUp() {
Dispatchers.setMain(mainThreadSurrogate)
repository = mock()
runBlocking {
whenever(repository.getBeerStyles()).thenReturn(successResource)
}
viewModel = BeerStylesViewModel(repository)
beerStylesObserver = mock()
}
#Test
fun `when load beerStyles success`() = runBlocking {
viewModel.beerStyles.observeForever(beerStylesObserver)
delay(10)
verify(repository).getBeerStyles()
verify(beerStylesObserver).onChanged(Resource.loading(null))
verify(beerStylesObserver).onChanged(successResource)
}
#ObsoleteCoroutinesApi
#ExperimentalCoroutinesApi
#After
fun tearDown() {
Dispatchers.resetMain()
mainThreadSurrogate.close()
}
}
I started to implement automated tests recently and I have a little doubt of how to test my ViewModel functions. My project follows some clean architecture concepts, I'm using lib kotlin for mockito (https://github.com/nhaarman/mockito-kotlin) and I would like to do the following test: When call makeLoginUser get success then set loginUserLiveData .
I have studied some concepts and I know the tests I should do, but I am still in doubt as to how to do them, which objects should I mock for a given test and which should be instantiated. I needed a small example to address me.
Here is my test class and some classes of the structure of my project.
TestClass
#RunWith(JUnit4::class)
class MainViewModelTest {
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Mock
private lateinit var myRepository: MyRepository
private lateinit var loginUserUseCase: LoginUserUseCase
private lateinit var checkUserAuthenticatedUsecase: CheckUserAuthenticatedUsecase
private lateinit var logoutUserUseCase: LogoutUserUseCase
private lateinit var mainViewModel: MainViewModel
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
}
#Test
fun `When call makeLoginUser get success then set loginUserLiveData`() {
// prepare
myRepository = mock { // should I mock or instantiate?
on { execLoginUser(ArgumentMatchers.anyString(), ArgumentMatchers.anyString()) } doReturn Flowable.just(true)
}
loginUserUseCase = mock { // should I mock or instantiate?
on { execute(ArgumentMatchers.anyMap()) } doReturn Flowable.just(true)
}
mainViewModel = MainViewModel(loginUserUseCase, checkUserAuthenticatedUsecase, logoutUserUseCase)
val observer = mock<Observer<ResultState<Boolean>>> { mock }
// Execute
mainViewModel.makeLoginUser("test#gmail.com", "123456")
// Check
// ?
}
}
ViewModel
// components 'LoginUserUseCase', 'CheckUserAuthenticatedUsecase' and 'LogoutUserUseCase' injected via koin
class MainViewModel(
private val loginUserUseCase: LoginUserUseCase,
private val checkUserAuthenticatedUsecase: CheckUserAuthenticatedUsecase,
private val logoutUserUsecase: LogoutUserUseCase
): BaseViewModel() {
val loginUserLiveData = MutableLiveData<ResultState<Boolean>>()
val userAuthenticatedLiveData = MutableLiveData<ResultState<Boolean>>()
val logoutUserLiveData = MutableLiveData<ResultState<Boolean>>()
fun makeLoginUser(email: String, password: String) {
loginUserLiveData.postValue(ResultState.Loading())
loginUserUseCase.execute(mapOf(EMAIL to email, PASSWORD to password))
.subscribe({
loginUserLiveData.postValue(ResultState.Success(it))
}, {
loginUserLiveData.postValue(ResultState.Error(it))
}).addTo(disposables)
}
...
}
UseCase Domain Class
// components 'ThreadExecutor' and 'PostExecutionThread' injected via koin
abstract class FlowableUseCase<T, in Params> constructor(
private val threadExecutor: ThreadExecutor,
private val postExecutionThread: PostExecutionThread) {
protected abstract fun buildUseCaseObservable(params: Params? = null): Flowable<T>
open fun execute(params: Params? = null): Flowable<T> {
return this.buildUseCaseObservable(params)
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.scheduler)
}
}
Abstraction UseCases
// components 'MyRepository', 'ThreadExecutor' and 'PostExecutionThread' injected via koin
// 'MyRepository' abstracts remote and local data repository
class LoginUserUseCase(
private val myRepository: MyRepository,
threadExecutor: ThreadExecutor,
postExecutionThread: PostExecutionThread
): FlowableUseCase<Boolean, Map<String, Any>?>(threadExecutor, postExecutionThread) {
override fun buildUseCaseObservable(params: Map<String, Any>?) = myRepository
.execLoginUser(
params?.get(EMAIL) as String,
params[PASSWORD] as String
)
}
I clearly don't understand how to unit test business logic inside Transformation. In my specific case I need to test Transformations.map, but I guess Transformations.switchmap would be the same.
The following is just an example of my scenario, and what I'd like to achieve.
MyViewModel.kt
class MyViewModel: ViewModel() {
private val _sampleLiveDataIwannaTest : MutableLiveData<Int> = MutableLiveData()
val sampleLiveDataIWannaTest: Livedata<Int> = _sampleLiveDataIWannaTest
// ...
val liveDataImNotInterestedIn = Transformations.map(myRepository.streamingData){
streaming->
_sampleLiveDataIwannaTest.postValue(streaming.firstElementValue +streaming.lastElementValue)
streaming
}
// ...
}
With:
val liveDataImNotInteresedIn : LiveData<Foo>
myRepository.streamingData : LiveData<Foo>
myRepository.streamingData is a data source that wakes up the Transformations.map which, in turn, starts the business logic I'm interested in (the value posted in _sampleLiveDataIwannaTest). In this particular test, I don't care about anything else.
MyViewModelTest.kt
class MyViewModelTest {
#get:Rule val rule = InstantTaskExecutorRule()
#RelaxedMockK
lateinit var myRepository : MyRepository
#OverrideMockKs
lateinit var sut: MyViewModel
#Before
fun setUp() {
MockKAnnotations.init(this, relaxUnitFun = true)
}
#Test
fun Transformations_Test(){
sut.liveDataImNotInterestedIn.observeForever{}
// 1)I really don't know how to mock the livedata that returns from
// myRepository.streamingData . Something like this is correct?
// every{myRepository.streamingData}.returns{< LiveData of type Int > }
// 2) I wish to write this kind of test:
//
// assertEquals(5, sampleLiveDataIWannaTest.value)
}
I'm using MockK instead of Mockito.
The unit test code will look like this:
class MyViewModelTest {
#get:Rule
val rule = InstantTaskExecutorRule()
#RelaxedMockK
lateinit var myRepository : MyRepository
#RelaxedMockK
lateinit var mockedSampleLiveDataIWannaTest : Observer<Int>
#OverrideMockKs
lateinit var sut: MyViewModel
#Before
fun setUp() {
MockKAnnotations.init(this, relaxUnitFun = true)
}
#Test
fun Transformations_Test(){
val expected = (*YOUR EXPECTED DATA HERE FROM REPOSITORY*)
every { myRepository.streamingData() } answers { expected }
sut.sampleLiveDataIWannaTest.observeForever(mockedSampleLiveDataIWannaTest)
verify { myRepository.streamingData() }
verify() { mockedSampleLiveDataIWannaTest.onChanged(Int) }
confirmVerified(myRepository, mockedSampleLiveDataIWannaTest)
}
if your repository is using coroutines then change every to coEvery and verify to coVerify
to learn more about MockK: https://mockk.io/
i have this test class running with MockitoJUnitRunner before and then i added RoboLectric, now i use RobolectricTestRunner
So the first thing it tryed is running my old tests but just changing the runner and the test now always fail. I do not really understand what is happening here, i am just trying to make my old test work with RobolectricTestRunner without stop using Mockito.
My Code before changing to RoboLectric (TEST PASS SUCCESSFULLY)
#RunWith(MockitoJUnitRunner::class)
class LauncherViewModelTest {
companion object {
#ClassRule
#JvmField
val schedulers = RxImmediateeSchedulerRule()
}
#Rule
#JvmField
val rule = InstantTaskExecutorRule()
#Mock
private lateinit var mockContext: MyApplication
#Mock
private lateinit var mockedDatabase: MyDatabase
#Mock
private lateinit var session: Session
//
#Mock
lateinit var mockedDatabaseRxWrapper: DatabaseRxWrapper
/** Evaluated class **/
#InjectMocks
lateinit var launcherViewModel: LauncherViewModel
#Test
fun checkIfHasSessionSuccess() {
val sessionFlowable: Flowable<Session> = Flowable.fromCallable { session }
FieldSetter.setField(launcherViewModel,
launcherViewModel.javaClass.getDeclaredField("databaseRxWrapper"), mockedDatabaseRxWrapper)
doReturn(sessionFlowable).`when`(mockedDatabaseRxWrapper).getSession()
launcherViewModel.checkIfHasSession()
//$hasSession is a mutable live data
Assert.assertEquals(true, launcherViewModel.hasSession.value)
}
}
My Code after changing to RoboLectric : (DatabaseRxWrapper.getSession() returns always null even when i use Mockito.doReturn().when())
#RunWith(RobolectricTestRunner::class)
class LauncherViewModelTest {
companion object {
#ClassRule
#JvmField
val schedulers = RxImmediateeSchedulerRule()
}
#Rule
#JvmField
val rule = InstantTaskExecutorRule()
#get:Rule
val mockitoRule = MockitoJUnit.rule()
#Mock
private lateinit var mockContext: MyApplication
#Mock
private lateinit var mockedDatabase: MyDatabase
#Mock
private lateinit var session: Session
//
#Mock
lateinit var mockedDatabaseRxWrapper: DatabaseRxWrapper
/** Evaluated class **/
#InjectMocks
lateinit var launcherViewModel: LauncherViewModel
#Test
fun checkIfHasSessionSuccess() {
val sessionFlowable: Flowable<Session> = Flowable.fromCallable { session }
FieldSetter.setField(launcherViewModel,
launcherViewModel.javaClass.getDeclaredField("databaseRxWrapper"), mockedDatabaseRxWrapper)
doReturn(sessionFlowable).`when`(mockedDatabaseRxWrapper).getSession()
launcherViewModel.checkIfHasSession()
//$hasSession is a mutable live data
Assert.assertEquals(true, launcherViewModel.hasSession.value)
}
}
Class under Test
class LauncherViewModel(application: Application) : AndroidViewModel(application) {
#Inject
lateinit var databaseRxWrapper: DatabaseRxWrapper
val hasSession: MutableLiveData<Boolean> = MutableLiveData()
val application by lazy { getApplication<MyApplication>() }
init {
application.getAppComponent()?.inject(this)
}
fun saveLocation(location: Location) = sharedPreferenceManager.saveLocation(location)
fun checkIfHasSession() {
databaseRxWrapper.getSession()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
hasSession.postValue(true)
}, {
hasSession.postValue(false)
})
}
}
I found that you don't need the rule for rewriting the configuration of RX if you are running with Robolectric
So the code worked after deleting this classrule:
companion object {
#ClassRule
#JvmField
val schedulers = RxImmediateeSchedulerRule()
}