Skip to content

Instantly share code, notes, and snippets.

@marcouberti
Last active June 1, 2021 01:51
Show Gist options
  • Save marcouberti/288a838a7f88a6b77d931e3d7d237ac4 to your computer and use it in GitHub Desktop.
Save marcouberti/288a838a7f88a6b77d931e3d7d237ac4 to your computer and use it in GitHub Desktop.
Android Local Unit Test using Kotlin Coroutines and MockK. A CoroutineTestRule is needed if your tested classes use the Main dispatcher, otherwise an "IllegalStateException" is thrown. Also a InstantTaskExecutorRule is needed to avoid a "NullPointerException". Use runBlockingTest to skip all suspending delays. Use coEvery/coVerify to mock/verify…
dependencies {
def kotlin_version = "1.3.61"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"
...
testImplementation 'junit:junit:4.12'
testImplementation 'org.json:json:20140107'
// Koin testing tools
testImplementation 'org.koin:koin-test:2.1.4'
// MockK
testImplementation "io.mockk:mockk:1.9.3"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.2")
testImplementation "androidx.arch.core:core-testing:2.1.0"
}
import org.junit.rules.TestWatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.*
import org.junit.runner.Description
@ExperimentalCoroutinesApi
class CoroutineTestRule(val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()) : TestWatcher() {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(dispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()
dispatcher.cleanupTestCoroutines()
}
fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) =
dispatcher.runBlockingTest { block() }
}
class SetupViewModelTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
@get:Rule
val coroutineTestRule = CoroutineTestRule()
/**
* class under test
*/
lateinit var viewModel: SetupViewModel
@MockK(relaxed = true)
lateinit var setup : Setup
@MockK(relaxed = true)
lateinit var onboarding : Onboarding
@MockK(relaxed = true)
lateinit var welcome : Welcome
@MockK(relaxed = true)
lateinit var userManager : UserManager
@MockK(relaxed = true)
lateinit var repository : SetupRepository
@Before
fun before() {
MockKAnnotations.init(this, relaxUnitFun = true)
viewModel = SetupViewModel(setup, onboarding, welcome, userManager, repository)
}
@Test
fun `test setup fails if settings fails`() = coroutineTestRule.runBlockingTest {
every { setup.isComplete() } returns false
coEvery { repository.getSetting() } returns Response.error(500, "".toResponseBody())
coEvery { repository.getMe() } returns Response.success(Me())
viewModel.initializeApp()
// observe LiveData
viewModel.errorDuringSetup.observeForever { error ->
assertTrue(error)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment