ViewModel unit test - Using delays - android

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()
}
}

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.

Main Looper queued unexecuted runnables

I'm trying to run a unit test on my RecyclerView. For my first test, I want to see if the RecyclerView is displayed.
#RunWith(RobolectricTestRunner::class)
class WordListFragmentTest {
// Executes task sin the Architecture component in the same thread.
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
private lateinit var scenario: FragmentScenario<WordListFragment>
private lateinit var viewModel: MainViewModel
val word = Word("Word")
#Before
fun setup() {
viewModel = mock(MainViewModel::class.java)
scenario = launchFragment(
factory = MainFragmentFactory(viewModel),
fragmentArgs = null,
themeResId = R.style.Theme_Words,
initialState = Lifecycle.State.RESUMED
)
}
#Test
fun `recyclerView displayed`() {
onView(withId(R.id.recyclerView))
.check(matches(isDisplayed()))
}
After running the test I get the following error.
java.lang.Exception: Main looper has queued unexecuted runnables. This might be the cause of the test failure. You might need a shadowOf(getMainLooper()).idle() call.
This appears to be related to LiveData observer that submits the list in the fragment. If I comment out the submit function the test will run.
The Fragment.
class WordListFragment(private val viewModel: MainViewModel) : Fragment() {
...
private fun submitList() {
viewModel.wordList.observe(viewLifecycleOwner, {
it?.let {
rvAdapter.submitList(it)
}
})
}
}
MianViewModel
class MainViewModel #Inject constructor(
var repository: IWordRepository,
#IoDispatcher var ioDispatcher: CoroutineDispatcher
) : ViewModel() {
var wordList: LiveData<List<Word>> = repository.allWords
...
}
This link states Robolectric will default to LooperMode.LEGACY behavior, but this can be overridden by applying a #LooperMode(NewMode) annotation to a test package, test class, or test method, or via the 'robolectric.looperMode' system property. I'm still experiencing the same error when I run my test.

Hilt Instrumentation test with Workmanager not working

When I try to run an ActivityScenario in my application that contains a WorkManager I get the following error on start:
java.lang.IllegalStateException: WorkManager is not initialized properly. You have explicitly disabled WorkManagerInitializer in your manifest, have not manually called WorkManager#initialize at this point, and your Application does not implement Configuration.Provider.
Using the WorkManagerTestInitHelper from the work-test artifact doesnt help either.
The WorkManager is defined like this:
#Provides
#Singleton
fun provideWorkmanager(#ApplicationContext context: Context) = WorkManager.getInstance(context)
This is my test atm:
#HiltAndroidTest
#RunWith(AndroidJUnit4::class)
class LoginTest {
#get:Rule(order = 0)
var hiltRule = HiltAndroidRule(this)
#get:Rule(order = 1)
val activityRule = ActivityScenarioRule(MainActivity::class.java)
#Before
fun before() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val config = Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setExecutor(SynchronousExecutor())
.build()
WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
}
#Test
fun test() {
...
}
}
This is because #get:Rule executes before the #Before does, as per the Google Documentation:
This rule provides functional testing of a single activity. The activity under test is launched before each test annotated with #Test and before any method annotated with #Before. It's terminated after the test is completed and all methods annotated with #After are finished. To access the activity under test in your test logic, provide a callback runnable to ActivityScenarioRule.getScenario().onActivity().
In order to fix this, you would need to initialise the WorkManager in the test with WorkManagerTestInitHelper before you try to launch the activity.
To do this, you should avoid using ActivityScenarioRule and use ActivityScenario instead, you can do something like this:
#HiltAndroidTest
#RunWith(AndroidJUnit4::class)
class LoginTest {
private lateinit var scenario: ActivityScenario<MainActivity>
#get:Rule
var hiltRule = HiltAndroidRule(this)
#Before
fun before() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val config = Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setExecutor(SynchronousExecutor())
.build()
WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
scenario = launchActivity()
}
#Test
fun test() {
scenario.moveToState(Lifecycle.State.CREATED).onActivity {
activity -> // do some test with the activity
}
}
}
To take advantage of ActivitScenarioRule and ensure that the WorkManager is initialized first I created a custom JUnit rule.
class WorkManagerRule : TestRule {
override fun apply(base: Statement?, description: Description?): Statement {
return object : Statement() {
override fun evaluate() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val config = Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setExecutor(SynchronousExecutor())
.build()
WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
try {
base?.evaluate()
} finally {
Log.d("WorkManagerRule", "Do some teardown")
}
}
}
}
#RunWith(AndroidJUnit4::class)
#HiltAndroidTest
class MyTest {
#get:Rule(order = 0)
var hiltRule = HiltAndroidRule(this)
#get:Rule(order = 1)
var workMgrRule = WorkManagerRule()
#get:Rule(order = 2)
var activityRule = ActivityScenarioRule(MainActivity::class.java)
#Before
fun init() {
hiltRule.inject()
}
//Your test code here...
}
I used the instructions for creating JUnit rules found here.

How to test a function running a coroutine in an Android instrumented test?

In my Android Kotlin project, I have a class, with a function running a coroutine, that I want to test using an instrumented test (not unit test).
Here is what it looks like:
class DemoClass
{
fun demo(liveData: MutableLiveData<String>)
{
CoroutineScope(IO).launch {
val result = doStuff()
withContext(Main) { liveData.value = result }
}
}
}
In my instrumented test class, here is what I tried:
#RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest
{
#ExperimentalCoroutinesApi
#Test
fun testCoroutine() = runBlockingTest {
val demoClass = DemoClass()
val liveData = MutableLiveData<String>()
demoClass.demo(liveData)
assertEquals("demo", liveData.value)
}
}
Unfortunately, it's not working. It seems like runBlockingTest {} is only available for unit testing, not instrumented testing. Here is my error when I run the test:
java.lang.NoClassDefFoundError: Failed resolution of: Lkotlinx/coroutines/test/TestBuildersKt;
So how can I test DemoClass.demo() and the liveData value in an instrumented test?
Thanks.
EDIT
I also tried this:
#ExperimentalCoroutinesApi
#RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest
{
private val testDispatcher = TestCoroutineDispatcher()
#Before
fun setup() {
Dispatchers.setMain(testDispatcher)
}
#After
fun tearDown() {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
#Test
fun testCoroutine(): Unit = runBlocking {
val demoClass = DemoClass()
val liveData = MutableLiveData<String>()
demoClass.demo(liveData)
assertEquals("demo", liveData.value)
}
}
The test runs, but I got this:
java.lang.AssertionError:
Expected :demo
Actual :null
I've been using just runBlocking {} instead of runBlockingTest to success.
I know technically runBlockingTest is the right way I believe the major difference is how delays are handled.
Possibly try these flags in your test class to bypass that difference,
#JvmField
val instantExecutorRule = InstantTaskExecutorRule()
#ExperimentalCoroutinesApi
#get:Rule
var mainCoroutineRule = MainCoroutineRule()

How to unit test LiveData Transformations

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/

Categories

Resources