Android MVVM testing with Mockito, mock repository.observeValue() has problem - android

I'm trying to test MVVM with Mockito.
The architecture of MVVM is similar to Android architecture blueprints.
We observe data from repository as LiveData.
And try to test observed value like below code.
class SplashViewModel(
private val appRepository: AppRepository
) {
val appInfo: LiveData<AppInfo> = appRepository.observeAppInfo()
}
#Test
fun getAppInfo() {
`when`(appRepository.observeAppInfo())
.thenReturn(appInfoData)
assertEquals(appInfoData, viewModel.appInfo.getOrAwaitValue())
}
The crucial point is viewModel.appInfo returns null, despite of I used mockito.
The problem
The creation of ViewModel is faster than using mockito.
So appInfo property is initialized with null, cause it dosen't know what observeAppInfo() is.
First solution
At first, I just trying to solve this problem with custom getter. Like this.
class SplashViewModel(
private val appRepository: AppRepository
) {
val appInfo: LiveData<AppInfo> get() = appRepository.observeAppInfo()
}
Now every time I access to appInfo they just re evaluate the data.
But is has it's own problem.
In this situation appVersion is getting error.
val appVersion: LiveData<String> = appInfo.map {
...
}
So every transformation LiveData(Like Transformations, MediatorLiveData) must use custom getter too.
And I felt it's not a great solution.
How do you think of it?

You could use #BeforeClass to make sure your initialisation is run before the tests.
#BeforeClass
fun setup(){
appInfo = appRepository.observeAppInfo()
}
See more

Related

Creating Unit Tests for ViewModels

I have recently completed this (links below) codelabs tutorial which walks through how to implement Room with LiveData and Databinding in Kotlin.
https://codelabs.developers.google.com/codelabs/android-room-with-a-view-kotlin/
https://github.com/googlecodelabs/android-room-with-a-view/tree/kotlin
Following on from this, I want to write some tests around the ViewModel, however, the GitHub repository where the code is stored does not contain any (it has a few tests around the DAO, not what I am interested in for now).
The ViewModel I am trying to test looks like this:
class WordViewModel(application: Application) : AndroidViewModel(application) {
private val repository: WordRepository
// Using LiveData and caching what getAlphabetizedWords returns has several benefits:
// - We can put an observer on the data (instead of polling for changes) and only update the
// the UI when the data actually changes.
// - Repository is completely separated from the UI through the ViewModel.
val allWords: LiveData<List<Word>>
init {
val wordsDao = WordRoomDatabase.getDatabase(application, viewModelScope).wordDao()
repository = WordRepository(wordsDao)
allWords = repository.allWords
}
/**
* Launching a new coroutine to insert the data in a non-blocking way
*/
fun insert(word: Word) = viewModelScope.launch(Dispatchers.IO) {
repository.insert(word)
}
}
My test class for the ViewModel looks like this:
#RunWith(JUnit4::class)
class WordViewModelTest {
private val mockedApplication = mock<Application>()
#Test
fun checkAllWordsIsEmpty() {
val vm = WordViewModel(mockedApplication)
assertEquals(vm.allWords, listOf<String>())
}
}
I get an error saying java.lang.IllegalArgumentException: Cannot provide null context for the database. This error then points to this line in the WordViewModel: val wordsDao = WordRoomDatabase.getDatabase(application, viewModelScope).wordDao(). To get this to not crash, I believe I need to mock a lot of what is in the ViewModel, which I am fine with.
I would like to be able to run the test above and in the future, I would also like to mock return a list of data when repository.allWords is called. However, I am not sure how to do this. So my question is, how can I mock the following lines from WordViewModel to allow me to do this?
val wordsDao = WordRoomDatabase.getDatabase(application, viewModelScope).wordDao()
repository = WordRepository(wordsDao)
allWords = repository.allWords

JUnit test not sending values as parameters to function (Kotlin)

I am creating a simple junit test to test a function in my view model but the first assertion fails as the function I call returns null. When I debug the function I call has null parameters which is weird cause I pass them in.
I have spent time debugging and searching for why I am having that issue but I have found nothing that fixes my issue or tells me what the issue is.
#RunWith(MockitoJUnitRunner::class)
class CurrencyUnitTest {
#Rule
#JvmField
val rule = InstantTaskExecutorRule()
#Mock
val currencyViewModel : CurrencyViewModel = mock(CurrencyViewModel::class.java)
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
val rates: HashMap<String, Double> =
hashMapOf(
"USD" to 1.323234,
"GBP" to 2.392394,
"AUD" to 0.328429,
"KWR" to 893.4833
)
val currencyRates = MutableLiveData<Resource<CurrencyRatesData?>>()
val resource = Resource<CurrencyRatesData?>(Status.SUCCESS, CurrencyRatesData("CAD", rates, 0))
currencyRates.value = resource
`when`(currencyViewModel.currencyRatesData).thenReturn(currencyRates)
val baseCurrency = MutableLiveData<String>()
baseCurrency.value = "CAD"
`when`(currencyViewModel.baseCurrency).thenReturn(baseCurrency)
}
#Test
fun calculateValueTest() {
// this fails
assertEquals("0.36", currencyViewModel.calculateValue("AUD", "1.11"))
}
}
Mocked classes will not really be called. If you want to test your currencyViewModel.calculateValue() method, create a real object of that class and mock possible constructor arguments.
To add to what Ben has said: the class you want to test has to be a real object, not a mock. A mock "does nothing" per default, and only does what you do it to tell you, so to test it does not make any sense.
What you mock is the dependencies of the class you test, i.e. the objects you pass to its' constructor.
In short: if you want to test CurrencyViewModel, create an object of it instead of mocking it.

How to unit test retrofit call?

For Example I have a retrofit interface such as:
interface SampleService {
fun getSomething(#body someBody: SomeBody)
}
Now I have a class which uses this interface such as:
class UserRequester(val service: SampleService) {
fun doGetSomething(someValue: String) {
val response = service.getSomething(SomeBody(someValue))
// ...
}
}
I want to test this class but dont know how to mock it.
I'm trying the following:
val mockSampleService = mock()
val userRequester = UserRequester(mockSampleService)
val requestBody = SomeBody(someString))
when(mockSampleService.getSomething(requestBody)).return(myExpectedValue)
....
My problem is that since I create the request object inside the function, I could not make the mock when().thenReturn() to work since i am technically passing two different object.
How should I test this? Thanks in advance.
The mocking problem (UserRequester)
You are not able to mock the mockSampleService method because your class is creating the SomeBody object and is different from the SomeBody object you are creating in your test.
Now you have 2 options:
Use Mockito.any() in your test, in this way you basically say that whatever your method is gonna use as parameter you will return the mocked behaviour
Use a factory that given a someString returns you a SomeObject like this:
// the factory
class SomeObjectFactory{
fun createSomeObject(someString: String): SomeObject {
return SomeObject(someString)
}
}
//the class
class UserRequester(
val service: SampleService, val factory: SomeObjectFactory
) {
fun doGetSomething(someValue: String) {
val response = service.getSomething(factory.createSomeObject(someValue))
// ...
}
}
//the test
class MyTest{
#Test
fun myTestMethod(){
val mockSampleService = mock()
val factory = mock()
val someBody = mock()
val userRequester = UserRequester(mockSampleService, factory)
`when`(factory.createSomeObject(someString)).thenReturn(someBody)
`when`(mockSampleService.getSomething(someBody)).thenReturn(myExpectedValue)
//rest of the code
}
}
The second approach is the cleanest one.
Testing Retrofit calls (SampleService)
I wouldn't unit test a Retrofit call.
When you are dealing with frameworks, apis, databases, shared preferences is always preferable to do integration tests instead of unit tests.
In this way you are actually testing that your code is working with the outside world.
I suggest you to test Retrofit calls with MockWebServer (it's a library from Square, the same company that developed OkHttp and Retrofit).
This read may be also helpful.
Probably SomeBody is a plain value object, since Retrofit requests work with value objects. If you define the equals method for the SomeBody class then the eq matcher will work, and you can write using mockito-kotlin:
whenever(mockService.getSomething(eq(SomeBody(someString)))).thenReturn(stubbedResult)
Actually, you can omit the eq matcher, Mockito will use the equals method for matching.
If SomeBody is a Kotlin data class then the equals method is automatically defined by comparing the fields.
If for some reason you don't want to rely on equals, then you can use the argThat matcher defined in mockito-kotlin:
whenever(mockService.getSomething(argThat { theField == someValue })).thenReturn(stubbedResult)
The problem is that there is static dependency on SomeBody's constructor:
val response = service.getSomething(SomeBody(someValue))
What you could do to have control over the instantiation of SomeBody is to use a "provider" or "factory" object, you can inject it in the constructor and invoke it at the right time:
interface SampleService {
fun getSomething(someBody: SomeBody)
}
open class SomeBody(val body: String)
open class UserRequester(
val service: SampleService,
val someBodyProvider: (String) -> SomeBody
) {
fun doGetSomething(someValue: String) {
val response = service.getSomething(someBodyProvider(someValue))
}
}
And mock it in your tests:
val someValue = "foo"
val sampleService: SampleService = mock()
val someBody: SomeBody = mock()
val someBodyProvider: (String) -> SomeBody = mock {
on { invoke(someValue) }.thenReturn(someBody)
}
val userRequester = UserRequester(sampleService, someBodyProvider)
userRequester.doGetSomething("foo")
verify(sampleService).getSomething(someBody)
verify(someBodyProvider).invoke(someValue)
I used an anonymous function but you might as well make it an interface.

How to mock Android Lifecycle components?

I am trying to build an Android app following the recommended design structure.
Let's say, there is a UserRepository for handling the users. However, I would like to have certain settings in the app, for example "Show profile picture", "Sort by", etc. I would like to store these settings in a Room database, just like the Users.
According to my understanding, the cleanest way is to have a separate UserRepository and a SettingsRepository. And of course, the Settings should have a sort of Model, let's call it SettingsModel, in order to be able to retrieve the Settings as a Map for example. Note, that this is not a ViewModel, it has nothing to do with the UI.
Then, the UserRepository should implement its own business (handling the users), just like in the example linked above. Besides that, it also should have a dependency of that SettingsModel, so that it can easily retrieve the settings, which affect how the Users should be retrieved.
The SettingsModel needs to turn the "raw database data" into a map, so that I can reach the settings like this: settings.show_profile_pictures and settings.sort_by, etc.
To achieve this, I need to extract the data from the LiveData, which implies, that I need to observe that LiveData, so that I can update the Map, whenever the settings change.
And here comes the problem: the observe() method needs a LifecycleOwner, which I cannot provide in my tests.
1st attempt: mock with Mockito
It would be an instrumented test, becase that way I have access to an Activity, which is needed to retrieve the DAO.
I am trying to #Inject it with Dagger, but the mocking wasn't successful:
class SettingsRepositoryTest {
private lateinit var settingsDao: SettingsDao
#Mock
private lateinit var mockLifecycleOwner: LifecycleOwner
#Before
fun createDb(){
MockitoAnnotations.initMocks(LifecycleOwner::class.java)
val appContext = InstrumentationRegistry.getTargetContext()
val db = Room.inMemoryDatabaseBuilder(appContext, CurrencyConverterDb::class.java).allowMainThreadQueries().build()
settingsDao = db.settingsDao()
}
#Test
fun testSettingsMap() {
val repo = SettingsRepository(settingsDao, mockLifecycleOwner) // throws the exception here
}
}
The exception:
kotlin.UninitializedPropertyAccessException: lateinit property mockLifecycleOwner has not been initialized
at com.helmet91.currencyconverter.repositories.SettingsRepositoryTestInst.testSettingsMap(SettingsRepositoryTestInst.kt:46)
2nd attempt: use Roboelectric to create an AppCompatActivity, which is in fact a LifecycleOwner.
It is not an instrumented test, because Roboelectric doesn't work in the androidTest environment.
The Activity still has to be mocked, however, it throws a NullPointerException. The only way I can think of, is to go through this Exception stack, and mock everything in it, if it's even possible at all. But that sounds insane to me. There has to be a better solution.
class SettingsRepositoryTest {
private lateinit var settingsDao: SettingsDao
private lateinit var activity: AppCompatActivity
#Before
fun createDb(){
val built = Robolectric.buildActivity(MainActivity::class.java) // throws the exception here
val created = built.create()
val controller = created.start()
activity = controller.get() as AppCompatActivity
val db = Room.inMemoryDatabaseBuilder(activity, CurrencyConverterDb::class.java).allowMainThreadQueries().build()
settingsDao = db.settingsDao()
}
#Test
fun testSettingsMap() {
val repo = SettingsRepository(settingsDao, activity)
val settingsMap = repo.getSettings()
val settingsEntity = Settings(1, "show_flags", "1", "bool")
settingsDao.insert(settingsEntity)
assertTrue(settingsMap.show_flags)
}
}
The exception:
java.lang.NullPointerException
at org.robolectric.internal.bytecode.ShadowImpl.extract(ShadowImpl.java:17)
at org.robolectric.shadow.api.Shadow.extract(Shadow.java:25)
at org.robolectric.Shadows.shadowOf(Shadows.java:1215)
at org.robolectric.shadows.CoreShadowsAdapter.getMainLooper(CoreShadowsAdapter.java:23)
at org.robolectric.android.controller.ComponentController.<init>(ComponentController.java:29)
at org.robolectric.android.controller.ComponentController.<init>(ComponentController.java:21)
at org.robolectric.android.controller.ActivityController.<init>(ActivityController.java:33)
at org.robolectric.android.controller.ActivityController.of(ActivityController.java:25)
at org.robolectric.Robolectric.buildActivity(Robolectric.java:97)
at org.robolectric.Robolectric.buildActivity(Robolectric.java:93)
at com.helmet91.currencyconverter.repositories.SettingsRepositoryTest.createDb(SettingsRepositoryTest.kt:29)
Is it really impossible to test anything, that involves Lifecycle components?

Android - mocking issue

I have a custom class:
class MyClass {
var name = ""
fun changeName(newName: String) {
name = newName
}
}
and my testing class:
#Test
fun testVerifyMock() {
val instance: MyClass = mock()
instance.changeName("newname")
Assert.assertEquals("newname", instance.name)
}
I'm faily new to Unit Tests and I'm kinda stuck, can someone please point me to why I get this error:
java.lang.AssertionError:
Expected :newname
Actual :null
Basically the call instance.changeName("newname") doesn't seem to be changing the name since it's always null
Mockito mocks just ignore what you pass to their methods unless you explicitly tell them what to do. In the case of changeName, the parameter is just ignored and therefore the name will remain null. I don't see why you would use a mock here anyway, so just change to:
val instance = MyClass()
...
Here's a post on "when to use mock".

Categories

Resources