Skip to content

Instantly share code, notes, and snippets.

@dariush-fathie
Last active August 26, 2021 12:47
Show Gist options
  • Save dariush-fathie/8cbd880b4ab04628f7b1c855bc69f2a6 to your computer and use it in GitHub Desktop.
Save dariush-fathie/8cbd880b4ab04628f7b1c855bc69f2a6 to your computer and use it in GitHub Desktop.
Testable viewModel
interface DispatcherProvider {
val mainImmediate: CoroutineDispatcher
val io: CoroutineDispatcher
val default: CoroutineDispatcher
val unconfined: CoroutineDispatcher
}
@Singleton
class DispatcherProviderImpl @Inject constructor() : DispatcherProvider {
override val mainImmediate: CoroutineDispatcher
get() = Dispatchers.Main.immediate
override val io: CoroutineDispatcher
get() = Dispatchers.IO
override val default: CoroutineDispatcher
get() = Dispatchers.Default
override val unconfined: CoroutineDispatcher
get() = Dispatchers.Unconfined
}
open class BaseViewModel(dispatcherProvider: DispatcherProvider) : ViewModel(),
DispatcherProvider by dispatcherProvider
@HiltViewModel
class MainViewModel @Inject constructor(
dispatcherProvider: DispatcherProvider
) : BaseViewModel(dispatcherProvider) {
var isSaved = false
fun saveDataOnIO() {
onIO {
delay(5000)
isSaved = true
}
}
var isSessionExpired = false
fun checkSessionExpired(){
onMain {
delay(10000)
isSessionExpired = true
}
}
}
class MainViewModelTest {
@ExperimentalCoroutinesApi
@get:Rule
var mainCoroutineRule = MainCoroutineRule()
@ExperimentalCoroutinesApi
val mainViewModel = MainViewModel(
TestDispatchersProvider(mainCoroutineRule.testCoroutineDispatcher)
)
@ExperimentalCoroutinesApi
@Test
fun saveDataOnIODispatcher() {
mainCoroutineRule.runBlockingTest {
mainViewModel.saveDataOnIO()
advanceUntilIdle()
assertTrue(mainViewModel.isSaved)
}
mainCoroutineRule.testCoroutineDispatcher.cleanupTestCoroutines()
}
@ExperimentalCoroutinesApi
@Test
fun `session must be expired after 10 seconds`() {
mainCoroutineRule.runBlockingTest {
mainViewModel.checkSessionExpired()
advanceUntilIdle()
assertTrue(mainViewModel.isSessionExpired)
}
mainCoroutineRule.testCoroutineDispatcher.cleanupTestCoroutines()
}
}
inline fun BaseViewModel.onMain(
crossinline body: suspend CoroutineScope.() -> Unit
): Job {
// by default, Dispatchers.Main is default dispatcher to viewModelScope
return viewModelScope.launch() {
body(this)
}
}
inline fun BaseViewModel.onMainImmediate(
crossinline body: suspend CoroutineScope.() -> Unit
): Job {
// make sure to read documentation about immediate dispatcher, it can throw Exception!
return viewModelScope.launch(mainImmediate) {
body(this)
}
}
inline fun BaseViewModel.onIO(
crossinline body: suspend CoroutineScope.() -> Unit
): Job {
return viewModelScope.launch(io) {
body(this)
}
}
inline fun BaseViewModel.onDefault(
crossinline body: suspend CoroutineScope.() -> Unit
): Job {
return viewModelScope.launch(default) {
body(this)
}
}
inline fun BaseViewModel.onUnconfined(
crossinline body: suspend CoroutineScope.() -> Unit
): Job {
return viewModelScope.launch(unconfined) {
body(this)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment