I'm trying to write an espresso test in my application. In the application, I use dagger 2 and architecture components (LiveData, etc.). The test has a function to help me create fake injections for the tested activity. When I use it to mock the ManViewModel it works with no problem and I can run the test. But when I want to set a mock value for the ViewModelProvider.Factory the test throws an error in MainActivity: ViewModelProviders.of(th…iewModelImpl::class.java) must not be null
I have debugged the test and when I'm assigning the mock value it isn't null and in the main activity the values are not null but steel I get the error.
Some help would be appreciated.
The code for MainActivity:
class MainActivity : BaseActivity(), AnimateFactsImage {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
#Inject
lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
mainViewModel = ViewModelProviders.of(this, viewModelFactory).get(MainViewModel::class.java)
// Here is the error: ViewModelProviders.of(th…iewModelImpl::class.java) must not be null
}
}
The code for MainActiviyTest:
#RunWith(AndroidJUnit4::class)
class MainActivityView {
#get:Rule
val activityTestRule = object : ActivityTestRule<MainActivity>(MainActivity::class.java, true, false) {
override fun beforeActivityLaunched() {
super.beforeActivityLaunched()
val myApp = InstrumentationRegistry.getTargetContext().applicationContext as MyApplication
myApp.activityDispatchingAndroidInjector = createFakeActivityInjector<MainActivity> {
mainViewModel = mainView
viewModelFactory = mockMPF // TODO: fix problem, null pointer in activity
}
}
}
private val mockMPF = Mockito.mock(ViewModelProvider.Factory::class.java)
private var mainView = Mockito.mock(MainViewModel::class.java)
private val repoLiveData = MutableLiveData<ApiResponse<Result>>()
#Before
fun setup() {
Mockito.`when`(mainView.getFactsList()).thenReturn(repoLiveData)
}
#Test
fun isListDisplayed() {
repoLiveData.postValue(ApiResponse(Result("title", arrayListOf(Row("title", "des", "img"))), null))
activityTestRule.launchActivity(null)
onView(withId(R.id.recycler_adapter)).check(matches(isDisplayed()))
}
}
Related
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.
I want to create a mock Object of an Activity with annotation #AndroidEntryPoint. After that mock it's methods like
whenever(activity.getAnalytics()).thenReturn(mockOfAnalytics)
but it doesn't work for activity annotated by #AndroidEntryPoint, when I remove this annotation it works - build.tool 4.2.2
#RunWith(PowerMockRunner::class)
#PrepareForTest(Html::class)
class CallShopViewModelTest {
...
#Mock
private lateinit var storefrontAnalytics: StorefrontAnalytics
#Mock
private lateinit var storefrontDelegate: StorefrontActivity.StorefrontDelegate
...
#Test
fun setShopObject() {
val mock = Mockito.mock(ShopMenuActivity::class.java)
whenever(mock.storefrontAnalytics).thenReturn(storefrontAnalytics)
whenever(mock.storefrontDelegate).thenReturn(storefrontDelegate)
whenever(mock.getString(ArgumentMatchers.anyInt(),
ArgumentMatchers.anyString())).thenReturn("test")
objectUnderTest = CallShopViewModel(mock)
objectUnderTest.setShop(Shop().apply {
isAcquired = false
shopId = 100
twilioPhone = "123"
})
Assert.assertFalse(objectUnderTest.mAcquired.get())
Assert.assertTrue(objectUnderTest.twilioFormattedText.get() != "")
}
}
Activity
#AndroidEntryPoint
class ShopMenuActivity : StorefrontActivity {
....
val storefrontAnalytics: StorefrontAnalytics
get() = app.storefrontAnalytics
val storefrontDelegates: StorefrontDelegates
get() = app.storefrontDelegates
}
So How can I mock this activity and use it's methods?
Thanks!
You better inject storefrontAnalytics: StorefrontAnalytics and val storefrontDelegates: StorefrontDelegates. Hilt plugin is rewriting your #AndroidEntryPoint annotated to a different decorator, this is probably the reason you are getting different results.
If you want to unit test your view model, you should not bring in the activity that hosts it. Provide mocked dependencies directly to the view model. Also why you are providing the activity to the view model constructor? This makes your unit testing excruciatingly painful.
Something like this:
#RunWith(PowerMockRunner::class)
#PrepareForTest(Html::class)
class CallShopViewModelTest {
...
#Mock
private lateinit var storefrontAnalytics: StorefrontAnalytics
#Mock
private lateinit var storefrontDelegate: StorefrontActivity.StorefrontDelegate
...
#Test
fun setShopObject() {
val mockStorefrontAnalytics : ... = mock()
val mockStorefrontDelegate : ... = mock()
whenever(mockStorefrontAnalytics).thenReturn(storefrontAnalytics)
whenever(mockStorefrontDelegate).thenReturn(storefrontDelegate)
objectUnderTest = CallShopViewModel(mockmockStorefrontAnalytics, mockStorefrontDelegate)
objectUnderTest.setShop(Shop().apply {
isAcquired = false
shopId = 100
twilioPhone = "123"
})
Assert.assertFalse(objectUnderTest.mAcquired.get())
Assert.assertTrue(objectUnderTest.twilioFormattedText.get() != "")
}
}
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'm try to test my ViewModel with mockito.
This is my TestClass:
#RunWith(JUnit4::class)
class RatesViewModelTest {
#Rule #JvmField
open val instantExecutorRule = InstantTaskExecutorRule()
#Mock
var observer: Observer<Pair<ArrayList<CurrencyExchangerModel>,Boolean>>? = null
#Mock
private lateinit var repository: RatesRepository
private var currencyList = ArrayList<CurrencyModel>()
private lateinit var viewModel : RatesViewModel
#Before
fun setUp(){
MockitoAnnotations.initMocks(this)
currencyList.add(CurrencyModel("BASE"))
viewModel = RatesViewModel(repository!!)
viewModel.getCurrencyExchangerObservableList().observeForever(observer!!)
}
#Test
fun testNull(){
Mockito.`when`(repository.getFlowableRates()).thenReturn( Flowable.just(currencyList) )
assertTrue(viewModel.getCurrencyExchangerObservableList().hasObservers())
}
}
When this method is invoked:
Mockito.`when`(repository.getFlowableRates()).thenReturn( Flowable.just(currencyList) )
I got this error:
kotlin.UninitializedPropertyAccessException: lateinit property db has
not been initialized
Here the repository:
open class RatesRepository(context:Context) : BaseRepository(context){
#Inject
lateinit var ratesAPI: RatesAPI
#Inject
lateinit var db: Database
/**
* load the updated chatList from API
*/
fun loadCurrencyRatesFromAPI(): Single<ArrayList<CurrencyModel>> {
val supportedCurrency = context.resources.getStringArray(R.array.currencies)
return ratesAPI.getLatestRates(EUR_CURRENCY_ID).map { RatesConverter.getRatesListFromDTO(it,supportedCurrency) }
}
/**
* save rates on DB
*/
fun saveCurrencyRatesOnDB(list:ArrayList<CurrencyModel>): Completable {
return db.currencyRatesDAO().insertAll(list)
}
/**
* get flawable rates from DB
*/
fun getFlowableRates(): Flowable<List<CurrencyModel>> {
return db.currencyRatesDAO().getAll()
}
companion object {
const val EUR_CURRENCY_ID = "EUR" //BASE
}
}
What i'm doing wrong ?
Thx !
When you define behaviour of a mock and use the when(...).then(...) notation of mockito,
the method itself is invoked (by mockito, normally not relevant for your test).
In your case that is a problem because db is not initialized.
To avoid this issues use the doReturn(...).when(...) syntax in these cases,
which does not cause the method invocation.
Mockito.doReturn(Flowable.just(currencyList)).when(repository).getFlowableRates();
(You might need to adjust this java syntax to make it kotlin compatible)
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()
}