Skip to content

Commit 78fa6be

Browse files
Kishan Kumar MauryaKishan Kumar Maurya
authored andcommitted
Unit testing using Spek framework
1 parent 0138b61 commit 78fa6be

File tree

10 files changed

+353
-17
lines changed

10 files changed

+353
-17
lines changed

app/build.gradle

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ apply plugin: 'kotlin-android'
55
apply plugin: 'kotlin-android-extensions'
66

77
apply plugin: 'kotlin-kapt'
8+
apply plugin: 'de.mannodermaus.android-junit5'
89

910
android {
1011
compileSdkVersion 29
@@ -35,6 +36,13 @@ android {
3536

3637
testOptions {
3738
unitTests.returnDefaultValues = true
39+
junitPlatform {
40+
filters {
41+
engines {
42+
include 'spek'
43+
}
44+
}
45+
}
3846
}
3947
}
4048

@@ -90,6 +98,7 @@ dependencies {
9098

9199
//testing
92100
testImplementation "android.arch.core:core-testing:1.1.1"
101+
androidTestImplementation 'androidx.arch.core:core-testing:2.1.0'
93102
testImplementation "com.jraska.livedata:testing-ktx:$jraska_version"
94103
testImplementation "junit:junit:$junit_version"
95104
testImplementation "org.mockito:mockito-inline:$mockito_version"
@@ -98,4 +107,11 @@ dependencies {
98107
testImplementation "org.hamcrest:hamcrest-library:$hamcrest_version"
99108
testImplementation "com.google.truth:truth:1.0"
100109

110+
//Spek
111+
testImplementation 'org.junit.platform:junit-platform-engine:1.6.2'
112+
testImplementation 'org.jetbrains.spek:spek-api:1.1.5'
113+
testImplementation 'org.spekframework.spek2:spek-dsl-jvm:2.0.6'
114+
testImplementation 'org.spekframework.spek2:spek-runner-junit5:2.0.6'
115+
testImplementation 'org.jetbrains.spek:spek-junit-platform-engine:1.1.5'
116+
testImplementation 'org.junit.platform:junit-platform-runner:1.4.0'
101117
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.example.githubfirebaseissue.common
2+
3+
import io.reactivex.Scheduler
4+
import io.reactivex.android.schedulers.AndroidSchedulers
5+
import io.reactivex.schedulers.Schedulers
6+
7+
interface RxScheduler {
8+
val io: Scheduler
9+
val main: Scheduler
10+
}
11+
12+
class BaseSchedulerProvider: RxScheduler {
13+
override val io: Scheduler
14+
get() = Schedulers.io()
15+
override val main: Scheduler
16+
get() = AndroidSchedulers.mainThread()
17+
}

app/src/main/java/com/example/githubfirebaseissue/di/module/ApiModule.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package com.example.githubfirebaseissue.di.module
22

33
import com.example.githubfirebaseissue.api.ApiConstant
44
import com.example.githubfirebaseissue.api.GithubApi
5+
import com.example.githubfirebaseissue.common.BaseSchedulerProvider
6+
import com.example.githubfirebaseissue.common.RxScheduler
57
import dagger.Module
68
import dagger.Provides
79
import okhttp3.OkHttpClient
@@ -28,4 +30,8 @@ class ApiModule {
2830
return retrofit.create(GithubApi::class.java)
2931
}
3032

33+
@Provides
34+
fun provideRxScheduler(): RxScheduler {
35+
return BaseSchedulerProvider()
36+
}
3137
}

app/src/main/java/com/example/githubfirebaseissue/ui/viewmodel/MainViewModel.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData
44
import androidx.lifecycle.MutableLiveData
55
import androidx.lifecycle.ViewModel
66
import com.example.githubfirebaseissue.common.Event
7+
import com.example.githubfirebaseissue.common.RxScheduler
78
import com.example.githubfirebaseissue.model.Comment
89
import com.example.githubfirebaseissue.model.Issue
910
import com.example.githubfirebaseissue.usecase.GetFireBaseIssueUseCase
@@ -13,7 +14,9 @@ import javax.inject.Inject
1314
import io.reactivex.schedulers.Schedulers
1415

1516

16-
class MainViewModel @Inject constructor(private val useCase: GetFireBaseIssueUseCase) :
17+
class MainViewModel @Inject constructor(private val useCase: GetFireBaseIssueUseCase,
18+
private val scheduler : RxScheduler
19+
) :
1720
ViewModel() {
1821

1922
private val _issueLiveData by lazy { MutableLiveData<Event<List<Issue>>>() }
@@ -32,8 +35,8 @@ class MainViewModel @Inject constructor(private val useCase: GetFireBaseIssueUse
3235

3336
fun fetchFireBaseIosIssueList() {
3437
val issueDisposable = useCase.getFireBaseIosIssues()
35-
.subscribeOn(Schedulers.io())
36-
.observeOn(AndroidSchedulers.mainThread())
38+
.subscribeOn(scheduler.io)
39+
.observeOn(scheduler.main)
3740
.doOnSubscribe { loadingState.value = true }
3841
.doOnEvent { _, _ -> loadingState.value = false }
3942
.doOnError { loadingState.value = false }
@@ -48,8 +51,8 @@ class MainViewModel @Inject constructor(private val useCase: GetFireBaseIssueUse
4851

4952
fun fetchIssueComments(number: Int) {
5053
val commentDisposable = useCase.getFireBaseIssuesComments(number)
51-
.subscribeOn(Schedulers.io())
52-
.observeOn(AndroidSchedulers.mainThread())
54+
.subscribeOn(scheduler.io)
55+
.observeOn(scheduler.main)
5356
.doOnSubscribe { loadingState.value = true }
5457
.doOnEvent { _, _ -> loadingState.value = false }
5558
.doOnError { loadingState.value = false }
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package com.example.githubfirebaseissue.repository
2+
3+
import com.example.githubfirebaseissue.api.GithubApi
4+
import com.example.githubfirebaseissue.model.Comment
5+
import com.example.githubfirebaseissue.model.Issue
6+
import com.nhaarman.mockitokotlin2.mock
7+
import io.reactivex.Single
8+
import okhttp3.MediaType.Companion.toMediaTypeOrNull
9+
import okhttp3.ResponseBody.Companion.toResponseBody
10+
import org.jetbrains.spek.api.Spek
11+
import org.jetbrains.spek.api.dsl.it
12+
import org.junit.platform.runner.JUnitPlatform
13+
import org.junit.runner.RunWith
14+
import org.mockito.Mockito
15+
import org.mockito.Mockito.*
16+
import retrofit2.HttpException
17+
import retrofit2.Response
18+
19+
@RunWith(JUnitPlatform::class)
20+
class GithubRepositoryImplTestSpek : Spek({
21+
22+
lateinit var endpoint: GithubApi
23+
24+
group("Github Repository Impl Test using Spek") {
25+
group("When firebase ios issues fetch api called")
26+
{
27+
beforeEachTest {
28+
endpoint = mock()
29+
}
30+
test("When firebase ios issues fetch api successful") {
31+
it("Should return list of issues") {
32+
val expectedResponse = listOf(Mockito.mock(Issue::class.java))
33+
`when`(endpoint.getFireBaseIosIssues()).thenReturn(Single.create {
34+
it.onSuccess(
35+
expectedResponse
36+
)
37+
})
38+
endpoint.getFireBaseIosIssues()
39+
.test()
40+
.assertComplete()
41+
.assertNoErrors()
42+
.assertValue(expectedResponse)
43+
44+
verify(endpoint).getFireBaseIosIssues()
45+
}
46+
}
47+
48+
test("When firebase ios issues fetch api failed") {
49+
it("Should return error") {
50+
val body = Response.error<Issue>(
51+
400,
52+
"{\"key\":[\"something went wrong\"]}".toResponseBody("application/json".toMediaTypeOrNull())
53+
)
54+
val exception = HttpException(body)
55+
56+
`when`(endpoint.getFireBaseIosIssues()).thenReturn(Single.error(exception))
57+
endpoint.getFireBaseIosIssues()
58+
.test()
59+
.assertValueCount(0)
60+
.assertError { it is HttpException }
61+
62+
verify(endpoint).getFireBaseIosIssues()
63+
}
64+
}
65+
}
66+
67+
group("When firebase ios user comment api called") {
68+
test("When firebase ios user comment api successful") {
69+
it("Should return list of comments") {
70+
val expectedResponse = listOf(mock(Comment::class.java))
71+
`when`(endpoint.getFireBaseIosUserComments(123)).thenReturn(Single.create {
72+
it.onSuccess(
73+
expectedResponse
74+
)
75+
})
76+
endpoint.getFireBaseIosUserComments(123)
77+
.test()
78+
.assertComplete()
79+
.assertNoErrors()
80+
.assertValue(expectedResponse)
81+
82+
verify(endpoint).getFireBaseIosUserComments(123)
83+
}
84+
}
85+
86+
test("When firebase ios user comment api failed") {
87+
it("Should return error") {
88+
val body = Response.error<Comment>(
89+
400,
90+
"{\"key\":[\"something went wrong\"]}".toResponseBody("application/json".toMediaTypeOrNull())
91+
)
92+
val exception = HttpException(body)
93+
94+
`when`(endpoint.getFireBaseIosUserComments(1234)).thenReturn(Single.error(exception))
95+
endpoint.getFireBaseIosUserComments(1234)
96+
.test()
97+
.assertValueCount(0)
98+
.assertError { it is HttpException }
99+
100+
verify(endpoint).getFireBaseIosUserComments(1234)
101+
}
102+
}
103+
}
104+
}
105+
})

app/src/test/java/com/example/githubfirebaseissue/ui/viewmodel/MainViewModelTest.kt

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import org.mockito.MockitoAnnotations
1414
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
1515
import androidx.lifecycle.Observer
1616
import com.example.githubfirebaseissue.common.Event
17+
import com.example.githubfirebaseissue.common.RxScheduler
1718
import com.jraska.livedata.TestObserver
19+
import com.nhaarman.mockitokotlin2.doReturn
20+
import com.nhaarman.mockitokotlin2.mock
21+
import io.reactivex.schedulers.Schedulers
1822
import junit.framework.Assert.assertEquals
1923
import org.junit.rules.TestRule
2024
import org.junit.runner.RunWith
@@ -35,25 +39,31 @@ class MainViewModelTest {
3539

3640
@Mock
3741
lateinit var fireBaseUseCase: GetFireBaseIssueUseCase
38-
42+
lateinit var scheduler: RxScheduler
3943

4044
private lateinit var viewModel: MainViewModel
4145
private lateinit var loadingJraskaTestObserver: TestObserver<Boolean>
4246
private lateinit var loadingJraskaTestStates: List<Boolean>
4347

4448

45-
@Mock lateinit var dataObserver: Observer<Event<List<Issue>>>
46-
@Mock lateinit var loadingObserver: Observer<Boolean>
47-
@Mock lateinit var errorObserver: Observer<Event<Throwable>>
49+
@Mock
50+
lateinit var dataObserver: Observer<Event<List<Issue>>>
51+
@Mock
52+
lateinit var loadingObserver: Observer<Boolean>
53+
@Mock
54+
lateinit var errorObserver: Observer<Event<Throwable>>
4855

4956
@Before
5057
fun setUp() {
5158
MockitoAnnotations.initMocks(this)
52-
viewModel = MainViewModel(fireBaseUseCase)
59+
scheduler = mock {
60+
on { io } doReturn Schedulers.trampoline()
61+
on { main } doReturn Schedulers.trampoline()
62+
}
63+
viewModel = MainViewModel(fireBaseUseCase, scheduler)
5364
}
5465

5566

56-
5767
@Test
5868
fun `should return issues when fetching FireBase ios issue using Jraska Library way`() {
5969
//Assemble
@@ -72,7 +82,7 @@ class MainViewModelTest {
7282
loadingJraskaTestObserver.assertHistorySize(2)
7383
assertEquals(true, loadingJraskaTestStates[0])
7484
assertEquals(false, loadingJraskaTestStates[1])
75-
assertEquals(dataObserver.value().getContentIfNotHandled(),issueList)
85+
assertEquals(dataObserver.value().getContentIfNotHandled(), issueList)
7686
}
7787

7888
@Test
@@ -94,7 +104,7 @@ class MainViewModelTest {
94104
assertEquals(true, loadingJraskaTestStates[0])
95105
assertEquals(false, loadingJraskaTestStates[1])
96106
assertEquals(false, loadingJraskaTestStates[2])
97-
assertEquals(errorObserver.value().getContentIfNotHandled(),throwable)
107+
assertEquals(errorObserver.value().getContentIfNotHandled(), throwable)
98108
}
99109

100110
@Test
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.example.githubfirebaseissue.ui.viewmodel
2+
3+
import androidx.arch.core.executor.ArchTaskExecutor
4+
import androidx.arch.core.executor.TaskExecutor
5+
import androidx.lifecycle.Observer
6+
import com.example.githubfirebaseissue.common.Event
7+
import com.example.githubfirebaseissue.common.RxScheduler
8+
import com.example.githubfirebaseissue.model.Issue
9+
import com.example.githubfirebaseissue.usecase.GetFireBaseIssueUseCase
10+
import com.nhaarman.mockitokotlin2.doReturn
11+
import com.nhaarman.mockitokotlin2.mock
12+
import com.nhaarman.mockitokotlin2.verify
13+
import io.reactivex.Single
14+
import io.reactivex.schedulers.Schedulers
15+
import org.jetbrains.spek.api.Spek
16+
import org.jetbrains.spek.api.dsl.context
17+
import org.jetbrains.spek.api.dsl.describe
18+
import org.jetbrains.spek.api.dsl.it
19+
import org.junit.Assert.assertEquals
20+
import org.junit.platform.runner.JUnitPlatform
21+
import org.junit.runner.RunWith
22+
import org.mockito.Mockito
23+
import org.mockito.Mockito.`when`
24+
25+
@RunWith(JUnitPlatform::class)
26+
class MainViewModelTestSpek : Spek({
27+
28+
lateinit var fireBaseUseCase: GetFireBaseIssueUseCase
29+
lateinit var viewModel: MainViewModel
30+
lateinit var scheduler: RxScheduler
31+
lateinit var dataObserver: Observer<Event<List<Issue>>>
32+
lateinit var loadingObserver: Observer<Boolean>
33+
lateinit var errorObserver: Observer<Event<Throwable>>
34+
35+
describe("MainViewModel Test Spek") {
36+
describe("When fetching FireBase ios issue From API") {
37+
beforeEachTest {
38+
fireBaseUseCase = mock()
39+
scheduler = mock {
40+
on { io } doReturn Schedulers.trampoline()
41+
on { main } doReturn Schedulers.trampoline()
42+
}
43+
viewModel = MainViewModel(fireBaseUseCase, scheduler)
44+
dataObserver = mock()
45+
loadingObserver = mock()
46+
errorObserver = mock()
47+
// In order to test LiveData, the `InstantTaskExecutorRule` rule needs to be applied via JUnit.
48+
// As we are running it with Spek, the "rule" will be implemented in this way instead
49+
ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
50+
override fun executeOnDiskIO(runnable: Runnable) { runnable.run() }
51+
override fun isMainThread(): Boolean { return true }
52+
override fun postToMainThread(runnable: Runnable) { runnable.run() }
53+
})
54+
}
55+
afterEachTest { ArchTaskExecutor.getInstance().setDelegate(null) }
56+
context("When firebase ios issues fetch api successful") {
57+
it("Should return list of issues") {
58+
val issueList = listOf(Mockito.mock(Issue::class.java))
59+
`when`(fireBaseUseCase.getFireBaseIosIssues()).thenReturn(Single.just(issueList))
60+
61+
viewModel.issueLiveData.observeForever(dataObserver)
62+
viewModel.loadingState.observeForever(loadingObserver)
63+
viewModel.apiError.observeForever(errorObserver)
64+
65+
viewModel.fetchFireBaseIosIssueList()
66+
67+
verify(loadingObserver).onChanged(true)
68+
verify(loadingObserver).onChanged(false)
69+
assertEquals(
70+
viewModel.issueLiveData.value?.getContentIfNotHandled(),
71+
issueList
72+
)
73+
assertEquals(viewModel.apiError.value?.getContentIfNotHandled(), null)
74+
}
75+
}
76+
77+
context("When firebase ios issues fetch api failed") {
78+
it("Should return error") {
79+
val throwable = Throwable("Something went wrong")
80+
`when`(fireBaseUseCase.getFireBaseIosIssues()).thenReturn(Single.error(throwable))
81+
82+
viewModel.issueLiveData.observeForever(dataObserver)
83+
viewModel.loadingState.observeForever(loadingObserver)
84+
viewModel.apiError.observeForever(errorObserver)
85+
86+
viewModel.fetchFireBaseIosIssueList()
87+
88+
verify(loadingObserver).onChanged(true)
89+
assertEquals(viewModel.issueLiveData.value?.getContentIfNotHandled(), null)
90+
assertEquals(viewModel.apiError.value?.getContentIfNotHandled(), throwable)
91+
}
92+
}
93+
}
94+
}
95+
})

0 commit comments

Comments
 (0)