Android Presenter Testing IllegalStateException captor.capture() must not be null - android

I have simple application based on mvp. Write test for presenter. Used Mockito for mock data. I catch view callback data (ArrayList) with ArgumentCaptor. My Test class
#RunWith(MockitoJUnitRunner::class)
class MainPresenterTest{
#Mock
lateinit var view:MainView
#Mock
lateinit var context:Context
#InjectMocks
lateinit var presenter: MainPresenter
#Captor
lateinit var captor: ArgumentCaptor<ArrayList<News>>
#Before
fun init(){
MockitoAnnotations.initMocks(this)
}
#Test
fun success(){
presenter.loadNews()
Mockito.verify<MainView>(view).onSuccess(captor.capture())
var data = captor.value
Mockito.verify(view).onSuccess(data)
Mockito.verify(view,never()).onError("")
}
}
Main View
interface MainView{
fun onSuccess(n:ArrayList<News>)
fun onError(e:String)
}
But throw
java.lang.IllegalStateException: captor.capture() must not be null
Example of correct verification:
verify(mock).doSomething()
Also, this error might show up because you verify either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.

You actually don't need to define it as a class lateinit variable. In your test method, define a method variable like this
#Test
fun success(){
presenter.loadNews()
val captor: ArgumentCaptor<ArrayList<*>> = ArgumentCaptor.forClass(ArrayList::class.java)
Mockito.verify<MainView>(view).onSuccess(captor.capture())
var data = captor.value
Mockito.verify(view).onSuccess(data)
Mockito.verify(view,never()).onError("")
}
}
Also, you should assert the data from the captor. Instead of this
Mockito.verify(view).onSuccess(data)
do something like this
assertEquals("x", data.size())

Related

Android test Fragmen with mock ViewModel using Hilt

I´m developing an app using Hilt, all works fine but when I try to run some Espresso test on a device running on below Android P I have encountered an issue.
The problem comes when I try to mock (using Mockk) the ViewModel so I can unit test my Fragment. When the Fragment will try to instanciate te ViewModel I got a NullPointerException when the ViewModel is being created. The NPE is thrown on the method setTagIfAbsent. The problem is that this method is package private as you can see on ViewModel source code, so it can not be mocked on Android < P.
I have tried by using the Kotlin All-Open plugin, it has helped on mocking the ViewModel and stubing it public methods. I try to stub the setTagIfAbsent by using the mockk private stubbing, like this:
every{
myViewModelMock["setTagIfAbsent"](any<String>,any())
} answers {secondArg()}
But when setTagIfAbsent is called, the real method is invoked, throwing the NPE because the ViewModel.mBagOfTags is null because the class is a mock.
The rest of the code is the following:
ViewModel:
#OpenForTesting
#HiltViewModel
class MyViewModel #Inject constructor MyViewModel(private val dependency: Dependency): ViewModel(){
//Rest of the code
}
Fragment:
#AndroidEntryPoint
class MyFragment: Fragment(){
private val viewModel: MyViewModel by viewModels()
//Rest of the code
}
Test class:
#HiltAndroidTest
#RunWith(AndroidJUnit4::class)
class MyFragmentTest {
#Bind
#MockK
lateinit var viewModel: MyViewModel
#get:Rule
var hiltRule = HiltAndroidRule(this)
#Before
fun prepareTest(){
MockkAnnotations.init(this)
hiltRule.inject()
launchFragmentInHiltContainer<MyFragment>()
}
#Test
fun testThatWillMakeAViewModelInvokation(){
onView(withId(R.id.button)).perform(click())
//Assume that button will make the ViewModel be called and created by the delegate
//When this happens the NPE is thrown
}
}
The method launchFragmentInHiltContainer comes from here (Hilt sample app).
If you look at the Mockk Android documentation it is said that < Android P the private methods can not be mocked (it is also said for finals, but the OpenClass plugin fix that problem).
Does anyone have an idea of how can I workaround this or how to fix the test?
Thanks in advance.
Instead of mocking setTagIfAbsent you can mock mBagOfTags using reflection on already mocked instance of ViewModel.
setInternalFieldValue(mockedViewModel, "mBagOfTags", HashMap<String, Any>())
fun setInternalFieldValue(target: Any, fieldName: String, value: Any, javaClass: Class<in Any> = target.javaClass) {
try {
val field = javaClass.getDeclaredField(fieldName)
field.isAccessible = true
field.set(target, value)
} catch (exception: NoSuchFieldException) {
val superClass = javaClass.superclass
if (superClass != null) {
setInternalFieldValue(target, fieldName, value, superClass)
} else {
throw RuntimeException("Field $fieldName is not declared in a hierarchy of this class")
}
}
}
If you use mockk(relaxed = true) this problem is solved

Android - mockito stub simple variable (not method)

I have a class with multiple variables inside.
SomeViewModel has a boolean variable defaulted to false,
var booleanVariable = false
SomeViewModel depends on SomeDataModel,
var dataModel: SomeDataModel? = null
Test class
#RunWith(MockitoJUnitRunner::class)
class TestClass {
#Mock lateinit var someViewModel: SomeViewModel
#Mock lateinit var someDataModel: SomeDataModel
#Before
fun setup() {
when(someViewModel.booleanVariable).thenReturn(true)
when(someViewModel.dataModel).thenReturn(someDataModel)
}
#Test
fun shouldShowImportPolicyTest() {
someViewModel.booleanVariable // return FALSE, not true as stubbed
someViewModel.dataModel // always returns NULL
}
}
if I stub a method it works fine. what am I doing wrong here?
You can mock member variables if they are not final with Mockito.
You have 3 choices here:
make them open
use the dependency mockito-inline to allow the mocking on final classes/fields
use the Kotlin all-open compiler plugin to make the class and its fields open in tests (https://kotlinlang.org/docs/reference/compiler-plugins.html)

Mockito does'nt mock repository

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)

How to correctly mock ViewModel on androidTest

I'm currently writing some UI unit tests for a fragment, and one of these #Test is to see if a list of objects is correctly displayed, this is not an integration test, therefore I wish to mock the ViewModel.
The fragment's vars:
class FavoritesFragment : Fragment() {
private lateinit var adapter: FavoritesAdapter
private lateinit var viewModel: FavoritesViewModel
#Inject lateinit var viewModelFactory: FavoritesViewModelFactory
(...)
Here's the code:
#MediumTest
#RunWith(AndroidJUnit4::class)
class FavoritesFragmentTest {
#Rule #JvmField val activityRule = ActivityTestRule(TestFragmentActivity::class.java, true, true)
#Rule #JvmField val instantTaskExecutorRule = InstantTaskExecutorRule()
private val results = MutableLiveData<Resource<List<FavoriteView>>>()
private val viewModel = mock(FavoritesViewModel::class.java)
private lateinit var favoritesFragment: FavoritesFragment
#Before
fun setup() {
favoritesFragment = FavoritesFragment.newInstance()
activityRule.activity.addFragment(favoritesFragment)
`when`(viewModel.getFavourites()).thenReturn(results)
}
(...)
// This is the initial part of the test where I intend to push to the view
#Test
fun whenDataComesInItIsCorrectlyDisplayedOnTheList() {
val resultsList = TestFactoryFavoriteView.generateFavoriteViewList()
results.postValue(Resource.success(resultsList))
(...)
}
I was able to mock the ViewModel but of course, that's not the same ViewModel created inside the Fragment.
So my question really, has someone done this successfully or has some pointers/references that might help me out?
Also, I've tried looking into the google-samples but with no luck.
For reference, the project can be found here: https://github.com/JoaquimLey/transport-eta/
Within your test setup you'll need to provide a test version of the FavoritesViewModelFactory which is being injected in the Fragment.
You could do something like the following, where the Module will need to be added to your TestAppComponent:
#Module
object TestFavoritesViewModelModule {
val viewModelFactory: FavoritesViewModelFactory = mock()
#JvmStatic
#Provides
fun provideFavoritesViewModelFactory(): FavoritesViewModelFactory {
return viewModelFactory
}
}
You'd then be able to provide your Mock viewModel in the test.
fun setupViewModelFactory() {
whenever(TestFavoritesViewModelModule.viewModelFactory.create(FavoritesViewModel::class.java)).thenReturn(viewModel)
}
I have solved this problem using an extra object injected by Dagger, you can find the full example here: https://github.com/fabioCollini/ArchitectureComponentsDemo
In the fragment I am not using directly the ViewModelFactory, I have defined a custom factory defined as a Dagger singleton:
https://github.com/fabioCollini/ArchitectureComponentsDemo/blob/master/uisearch/src/main/java/it/codingjam/github/ui/search/SearchFragment.kt
Then in the test I replace using DaggerMock this custom factory using a factory that always returns a mock instead of the real viewModel:
https://github.com/fabioCollini/ArchitectureComponentsDemo/blob/master/uisearchTest/src/androidTest/java/it/codingjam/github/ui/repo/SearchFragmentTest.kt
Look like, you use kotlin and koin(1.0-beta).
It is my decision for mocking
#RunWith(AndroidJUnit4::class)
class DashboardFragmentTest : KoinTest {
#Rule
#JvmField
val activityRule = ActivityTestRule(SingleFragmentActivity::class.java, true, true)
#Rule
#JvmField
val executorRule = TaskExecutorWithIdlingResourceRule()
#Rule
#JvmField
val countingAppExecutors = CountingAppExecutorsRule()
private val testFragment = DashboardFragment()
private lateinit var dashboardViewModel: DashboardViewModel
private lateinit var router: Router
private val devicesSuccess = MutableLiveData<List<Device>>()
private val devicesFailure = MutableLiveData<String>()
#Before
fun setUp() {
dashboardViewModel = Mockito.mock(DashboardViewModel::class.java)
Mockito.`when`(dashboardViewModel.devicesSuccess).thenReturn(devicesSuccess)
Mockito.`when`(dashboardViewModel.devicesFailure).thenReturn(devicesFailure)
Mockito.`when`(dashboardViewModel.getDevices()).thenAnswer { _ -> Any() }
router = Mockito.mock(Router::class.java)
Mockito.`when`(router.loginActivity(activityRule.activity)).thenAnswer { _ -> Any() }
StandAloneContext.loadKoinModules(hsApp + hsViewModel + api + listOf(module {
single(override = true) { router }
factory(override = true) { dashboardViewModel } bind ViewModel::class
}))
activityRule.activity.setFragment(testFragment)
EspressoTestUtil.disableProgressBarAnimations(activityRule)
}
#After
fun tearDown() {
activityRule.finishActivity()
StandAloneContext.closeKoin()
}
#Test
fun devicesSuccess(){
val list = listOf(Device(deviceName = "name1Item"), Device(deviceName = "name2"), Device(deviceName = "name3"))
devicesSuccess.postValue(list)
onView(withId(R.id.rv_devices)).check(ViewAssertions.matches(ViewMatchers.isCompletelyDisplayed()))
onView(withId(R.id.rv_devices)).check(matches(hasDescendant(withText("name1Item"))))
onView(withId(R.id.rv_devices)).check(matches(hasDescendant(withText("name2"))))
onView(withId(R.id.rv_devices)).check(matches(hasDescendant(withText("name3"))))
}
#Test
fun devicesFailure(){
devicesFailure.postValue("error")
onView(withId(R.id.rv_devices)).check(ViewAssertions.matches(ViewMatchers.isCompletelyDisplayed()))
Mockito.verify(router, times(1)).loginActivity(testFragment.activity!!)
}
#Test
fun devicesCall() {
onView(withId(R.id.rv_devices)).check(ViewAssertions.matches(ViewMatchers.isCompletelyDisplayed()))
Mockito.verify(dashboardViewModel, Mockito.times(1)).getDevices()
}
}
In the example you provided, you are using mockito to return a mock for a specific instance of your view model, and not for every instance.
In order to make this work, you will have to have your fragment use the exact view model mock that you have created.
Most likely this would come from a store or a repository, so you could put your mock there? It really depends on how you setup the acquisition of the view model in your Fragments logic.
Recommendations:
1) Mock the data sources the view model is constructed from or
2) add a fragment.setViewModel() and Mark it as only for use in tests. This is a little ugly, but if you don't want to mock data sources, it is pretty easy this way.
One could easily mock a ViewModel and other objects without Dagger simply by:
Create a wrapper class that can re-route calls to the ViewModelProvider. Below is the production version of the wrapper class that simply passes the calls to the real ViewModelProvider which is passed in as a parameter.
class VMProviderInterceptorImpl : VMProviderInterceptor { override fun get(viewModelProvider: ViewModelProvider, x: Class<out ViewModel>): ViewModel {
return viewModelProvider.get(x)
}
}
Adding getters and setters for this wrapper object to the Application class.
In the Activity rule, before an activity is launched, swap out the real wrapper with a mocked wrapper that does not route the get ViewModel call to the real viewModelProvider and instead provides a mocked object.
I realize this is not as powerful as dagger but the simplicity is attractive.

lateinit property presenter has not been initialized with EasyMVP, Robolectric and Kotlin

I have problem while trying to use Robolectric to test activity which is using EasyMvp.
All of the classes are written in Kotlin.
This is beginning of an activity:
#ActivityView(layout = R.layout.activity_access, presenter = AccessPresenterImpl::class)
class AccessActivity : BaseActivity(), AccessView {
#Presenter
lateinit var presenter: AccessPresenter
override fun providePresenter(): BasePresenter? {
return presenter
}
And at the onStart, every activity is initializing extras in presenter.
I was trying to introduce Robolectric tests in my app.
var activity: AccessActivity? = null
var loginEditText: EditText? = null
var passwordEditText: EditText? = null
#Before
fun initData() {
activity = Robolectric.setupActivity(AccessActivity::class.java)
loginEditText = activity?.findViewById(R.id.loginEditText)
passwordEditText = activity?.findViewById(R.id.passwordEditText)
}
But while Running the test, I am always getting error:
kotlin.UninitializedPropertyAccessException: lateinit property presenter has not been initialized
I assume that this is problem with Robolectric annotation proccessing. Is there any solution for that?

Categories

Resources