Skip to content

Commit f80a59c

Browse files
Kishan Kumar MauryaKishan Kumar Maurya
authored andcommitted
Viewmodel unit testing added
1 parent dfe05e6 commit f80a59c

File tree

4 files changed

+214
-5
lines changed

4 files changed

+214
-5
lines changed

app/build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ android {
3232
kotlinOptions {
3333
jvmTarget = "1.8"
3434
}
35+
36+
testOptions {
37+
unitTests.returnDefaultValues = true
38+
}
3539
}
3640

3741
dependencies {
@@ -92,5 +96,6 @@ dependencies {
9296
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.2'
9397
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
9498
testImplementation "org.hamcrest:hamcrest-library:$hamcrest_version"
99+
testImplementation "com.google.truth:truth:1.0"
95100

96101
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.example.githubfirebaseissue.testutil
2+
3+
import androidx.annotation.VisibleForTesting
4+
import androidx.lifecycle.LiveData
5+
import androidx.lifecycle.Observer
6+
import com.example.githubfirebaseissue.common.Event
7+
import java.util.concurrent.CountDownLatch
8+
import java.util.concurrent.TimeUnit
9+
import java.util.concurrent.TimeoutException
10+
11+
/**
12+
* Gets the value of a [LiveData] or waits for it to have one, with a timeout.
13+
*
14+
* Use this extension from host-side (JVM) tests. It's recommended to use it alongside
15+
* `InstantTaskExecutorRule` or a similar mechanism to execute tasks synchronously.
16+
*/
17+
18+
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
19+
fun <T> LiveData<T>.getOrAwaitValue(
20+
time: Long = 2,
21+
timeUnit: TimeUnit = TimeUnit.SECONDS,
22+
afterObserve: () -> Unit = {}
23+
): T {
24+
var data: T? = null
25+
val latch = CountDownLatch(1)
26+
val observer = object : Observer<T> {
27+
override fun onChanged(o: T?) {
28+
data = o
29+
latch.countDown()
30+
this@getOrAwaitValue.removeObserver(this)
31+
}
32+
}
33+
this.observeForever(observer)
34+
35+
try {
36+
afterObserve.invoke()
37+
38+
// Don't wait indefinitely if the LiveData is not set.
39+
if (!latch.await(time, timeUnit)) {
40+
this.removeObserver(observer)
41+
throw TimeoutException("LiveData value was never set.")
42+
}
43+
44+
} finally {
45+
this.removeObserver(observer)
46+
}
47+
@Suppress("UNCHECKED_CAST")
48+
return data as T
49+
}

app/src/test/java/com/example/githubfirebaseissue/testutil/Rx2SchedulersOverrideRule.kt

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
11
package com.example.githubfirebaseissue.testutil
22

3+
import io.reactivex.Scheduler
34
import io.reactivex.android.plugins.RxAndroidPlugins
5+
import io.reactivex.disposables.Disposable
6+
import io.reactivex.internal.schedulers.ExecutorScheduler
47
import io.reactivex.plugins.RxJavaPlugins
5-
import io.reactivex.schedulers.Schedulers
68
import org.junit.rules.TestRule
79
import org.junit.runner.Description
810
import org.junit.runners.model.Statement
11+
import java.util.concurrent.Executor
12+
import java.util.concurrent.TimeUnit
913

1014
class Rx2SchedulersOverrideRule : TestRule {
1115

16+
private val immediate = object : Scheduler() {
17+
override fun scheduleDirect(run: Runnable, delay: Long, unit: TimeUnit): Disposable {
18+
return super.scheduleDirect(run, 0, unit)
19+
}
20+
21+
override fun createWorker(): Worker {
22+
return ExecutorScheduler.ExecutorWorker(Executor { it.run() }, false)
23+
}
24+
}
25+
1226
override fun apply(base: Statement?, description: Description?): Statement =
1327
object : Statement() {
1428
override fun evaluate() {
15-
RxJavaPlugins.setIoSchedulerHandler { _ -> Schedulers.trampoline() }
16-
RxJavaPlugins.setComputationSchedulerHandler { _ -> Schedulers.trampoline() }
17-
RxJavaPlugins.setNewThreadSchedulerHandler { _ -> Schedulers.trampoline() }
18-
RxAndroidPlugins.setInitMainThreadSchedulerHandler { _ -> Schedulers.trampoline() }
29+
RxJavaPlugins.setIoSchedulerHandler { immediate }
30+
RxJavaPlugins.setComputationSchedulerHandler { immediate }
31+
RxJavaPlugins.setNewThreadSchedulerHandler { immediate }
32+
RxAndroidPlugins.setInitMainThreadSchedulerHandler { immediate }
33+
RxJavaPlugins.setInitComputationSchedulerHandler { immediate }
34+
RxJavaPlugins.setInitNewThreadSchedulerHandler { immediate }
35+
RxJavaPlugins.setInitSingleSchedulerHandler { immediate }
1936

2037
try {
2138
base?.evaluate()
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package com.example.githubfirebaseissue.ui.viewmodel
2+
3+
import com.example.githubfirebaseissue.model.Issue
4+
import com.example.githubfirebaseissue.testutil.Rx2SchedulersOverrideRule
5+
import com.example.githubfirebaseissue.usecase.GetFireBaseIssueUseCase
6+
import io.reactivex.Single
7+
import org.junit.Before
8+
9+
import org.junit.Rule
10+
import org.junit.Test
11+
import org.mockito.Mock
12+
import org.mockito.Mockito.`when`
13+
import org.mockito.MockitoAnnotations
14+
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
15+
import androidx.lifecycle.Observer
16+
import com.example.githubfirebaseissue.common.Event
17+
import com.jraska.livedata.TestObserver
18+
import junit.framework.Assert.assertEquals
19+
import org.junit.rules.TestRule
20+
import org.junit.runner.RunWith
21+
import org.mockito.Mockito.verify
22+
import org.mockito.junit.MockitoJUnitRunner
23+
24+
25+
@RunWith(MockitoJUnitRunner::class)
26+
class MainViewModelTest {
27+
28+
@JvmField
29+
@Rule
30+
val rx2SchedulersOverrideRule = Rx2SchedulersOverrideRule()
31+
32+
@JvmField
33+
@Rule
34+
var rule: TestRule = InstantTaskExecutorRule()
35+
36+
@Mock
37+
lateinit var fireBaseUseCase: GetFireBaseIssueUseCase
38+
39+
40+
private lateinit var viewModel: MainViewModel
41+
private lateinit var loadingJraskaTestObserver: TestObserver<Boolean>
42+
private lateinit var loadingJraskaTestStates: List<Boolean>
43+
44+
45+
@Mock lateinit var dataObserver: Observer<Event<List<Issue>>>
46+
@Mock lateinit var loadingObserver: Observer<Boolean>
47+
@Mock lateinit var errorObserver: Observer<Event<Throwable>>
48+
49+
@Before
50+
fun setUp() {
51+
MockitoAnnotations.initMocks(this)
52+
viewModel = MainViewModel(fireBaseUseCase)
53+
}
54+
55+
56+
57+
@Test
58+
fun `should return issues when fetching FireBase ios issue using Jraska Library way`() {
59+
//Assemble
60+
val issueList = listOf<Issue>()
61+
`when`(fireBaseUseCase.getFireBaseIosIssues()).thenReturn(Single.just(issueList))
62+
63+
loadingJraskaTestObserver = TestObserver.test(viewModel.loadingState)
64+
loadingJraskaTestStates = loadingJraskaTestObserver.valueHistory()
65+
66+
val dataObserver = TestObserver.test(viewModel.issueLiveData)
67+
68+
//Act
69+
viewModel.fetchFireBaseIosIssueList()
70+
71+
//Assert
72+
loadingJraskaTestObserver.assertHistorySize(2)
73+
assertEquals(true, loadingJraskaTestStates[0])
74+
assertEquals(false, loadingJraskaTestStates[1])
75+
assertEquals(dataObserver.value().getContentIfNotHandled(),issueList)
76+
}
77+
78+
@Test
79+
fun `should return error when fetching FireBase ios issue using Jraska Library way`() {
80+
//Assemble
81+
val throwable = Throwable("Something went wrong")
82+
`when`(fireBaseUseCase.getFireBaseIosIssues()).thenReturn(Single.error(throwable))
83+
84+
loadingJraskaTestObserver = TestObserver.test(viewModel.loadingState)
85+
loadingJraskaTestStates = loadingJraskaTestObserver.valueHistory()
86+
87+
val errorObserver = TestObserver.test(viewModel.apiError)
88+
89+
//Act
90+
viewModel.fetchFireBaseIosIssueList()
91+
92+
//Assert
93+
loadingJraskaTestObserver.assertHistorySize(3)
94+
assertEquals(true, loadingJraskaTestStates[0])
95+
assertEquals(false, loadingJraskaTestStates[1])
96+
assertEquals(false, loadingJraskaTestStates[2])
97+
assertEquals(errorObserver.value().getContentIfNotHandled(),throwable)
98+
}
99+
100+
@Test
101+
fun `should return issues when fetching FireBase ios issue using observer way`() {
102+
//Assemble
103+
val issueList = listOf<Issue>()
104+
`when`(fireBaseUseCase.getFireBaseIosIssues()).thenReturn(Single.just(issueList))
105+
106+
viewModel.issueLiveData.observeForever(dataObserver)
107+
viewModel.loadingState.observeForever(loadingObserver)
108+
viewModel.apiError.observeForever(errorObserver)
109+
110+
//Act
111+
viewModel.fetchFireBaseIosIssueList()
112+
113+
//Assert
114+
verify(loadingObserver).onChanged(true)
115+
verify(loadingObserver).onChanged(false)
116+
assertEquals(viewModel.issueLiveData.value?.getContentIfNotHandled(), issueList)
117+
assertEquals(viewModel.apiError.value?.getContentIfNotHandled(), null)
118+
}
119+
120+
@Test
121+
fun `should return error when fetching FireBase ios issue using observer way`() {
122+
//Assemble
123+
val throwable = Throwable("Something went wrong")
124+
`when`(fireBaseUseCase.getFireBaseIosIssues()).thenReturn(Single.error(throwable))
125+
126+
viewModel.issueLiveData.observeForever(dataObserver)
127+
viewModel.loadingState.observeForever(loadingObserver)
128+
viewModel.apiError.observeForever(errorObserver)
129+
130+
//Act
131+
viewModel.fetchFireBaseIosIssueList()
132+
133+
//Assert
134+
verify(loadingObserver).onChanged(true)
135+
assertEquals(viewModel.issueLiveData.value?.getContentIfNotHandled(), null)
136+
assertEquals(viewModel.apiError.value?.getContentIfNotHandled(), throwable)
137+
}
138+
}

0 commit comments

Comments
 (0)