Hello I have a piece of code that runs coroutine. I am trying to write unit test for it.But it seems to fail. I have added Coroutine test rule to add StandardTestDispatcher but that doesn't seem to work I guess.
Any ideas is much appreciated. I am trying to write unit tests for coroutines for the first time and got stuck.
Here is my simple presenter class that is using coroutines
class PostsActivityPresenter(
private val apiHelper: ApiFactory
) : CoroutineScope, PostsContract.Presenter {
private var view: PostsContract.View? = null
private val job: Job = Job()
override val coroutineContext: CoroutineContext = job + Dispatchers.IO
override fun attachView(view: PostsContract.View?) {
this.view = view
}
override fun getPostsandUserInfo() {
launch {
val posts = apiHelper.getPosts()
val users = apiHelper.getUsers()
val postsWithUserDetails = mutableListOf<PostsWithUserInfo>()
for (post in posts) {
users.firstOrNull { it.id == post.userId }?.let { user ->
postsWithUserDetails.add(PostsWithUserInfo(post, user))
}
}
withContext(Dispatchers.Main) { ///// Test will pass if this line is removed.
view?.displayPosts(postsWithUserDetails)
}
}
}
override fun detachView() {
job.cancel()
view = null
}
}
here is my unit test code:
class PostsPresenterTest {
#RelaxedMockK
private lateinit var apiHelper: ApiHelper
#io.mockk.impl.annotations.MockK
private lateinit var view: PostsContract.View
#ExperimentalCoroutinesApi
#get:Rule
var coroutinesTestRule = CoroutineTestRule()
private lateinit var subject: PostsActivityPresenter
private val posts = listOf(
Post(
id = 1,
userId = 12,
title = "title 1",
body = "body 1"
)
)
private val users = listOf(
User(
name = "Name1",
username = "Username1",
email = "email1#gmail.com",
id = 12
),
User(
name = "Name2",
username = "Username2",
email = "email2#gmail.com",
id = 13
)
)
#Before
fun setUp() {
MockKAnnotations.init(this, relaxed = true)
subject = PostsActivityPresenter((apiHelper))
}
#ExperimentalCoroutinesApi
#Test
fun `test if posts is being called`() = runBlocking {
// arrange
coEvery { apiHelper.getPosts() }.returns(posts)
coEvery { apiHelper.getUsers() }.returns(users)
every { view.displayPosts(any()) }.returns(Unit)
// act
subject.attachView(view)
subject.getPostsandUserInfo()
// assert
coVerify {
view.displayPosts(
listOf(
PostsWithUserInfo(
post = Post(
userId = 12,
id = 1,
title = "title 1",
body = "body 1"
),
user = User(
id = 12,
name = "Name1",
username = "Username1",
email = "email1#gmail.com",
)
)
)
)
}
}
}
#ExperimentalCoroutinesApi
class CoroutineTestRule(private val testDispatcher: TestDispatcher = StandardTestDispatcher()) : TestWatcher() {
override fun starting(description: Description) {
super.starting(description)
Dispatchers.setMain(testDispatcher)
}
override fun finished(description: Description) {
super.finished(description)
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
}
Related
I have two stateflow in my viewmodel
private val _peopleList = MutableStateFlow(emptyList<People>())
val peopleList: StateFlow<List<People>> = _peopleList
val _peopleListLoader = MutableStateFlow(false)
val peopleListLoader: StateFlow<Boolean> = _peopleListLoader
peopleList is used to display list and peopleListLoader is used to display progress indicator in UI. All of these are working fine in app as expected. But in my Unit test when I check the peopleListLoader using peopleListLoader.toList(values) functionality its dosent have the values i assigned to it during the peoplelist loading.
Following is my implementation
PeopleListViewModel
#HiltViewModel
class PeopleListViewModel #Inject constructor(
val repository: PeopleRepository,
#MainDispatcher private val dispatcher: CoroutineDispatcher
) : ViewModel() {
private val _peopleList = MutableStateFlow(emptyList<People>())
val peopleList: StateFlow<List<People>> = _peopleList
val _peopleListLoader = MutableStateFlow(false)
val peopleListLoader: StateFlow<Boolean> = _peopleListLoader
private val _peopleListErrorMessage = MutableStateFlow("")
var peopleListErrorMessage: StateFlow<String> = _peopleListErrorMessage
fun loadPeoplesList() {
viewModelScope.launch(dispatcher) {
_peopleListLoader.value = true // set the loader visibility true
repository.getPeopleList().collect() {
try {
when {
it.isSuccess -> {
_peopleList.value = it.getOrNull()!!
_peopleListErrorMessage.value = ""
}
it.isFailure -> {
_peopleListErrorMessage.value = it.exceptionOrNull().toString()
}
}
}finally {
_peopleListLoader.value = false // set the loader visibility false
}
}
}
}
}
Unit Test
class PeopleListViewModelShould {
#get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
//SUT - PeopleListViewModel
val repository: PeopleRepository = mock()
val peoplesList = mock<List<People>>()
val expected = Result.success(peoplesList)
#Test
fun turnLoaderVisibilityFalseAfterFetchingPeoplesList()= runTest{
whenever(repository.getPeopleList() ).thenReturn(
flow {
emit(expected)
}
)
val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
val viewmodel = PeopleListViewModel(repository,testDispatcher)
val values = mutableListOf<Boolean>()
val collectJob= launch(testDispatcher){
viewmodel.peopleListLoader.toList(values)
}
viewmodel.loadPeoplesList()
System.out.println("Result is : "+values.toString()) //This prints ony [false] iam expecting [false,true,false]
assertEquals(true, values[0]) // Assert on the list contents
collectJob.cancel()
}
}
WHats is wrong with my unittest, I appreciate all suggestions and advance thanks to all..
I am begginer in testing android apps. I have problem while testing my viewmodel. I want to test view model function which gets data from repository and make some logic with it.
class CurrencyViewModel(
private val repository: Repository
) : ViewModel() {
private var day: LocalDate = LocalDate.now()
private val listWithFormattedCurrencies = mutableListOf<CurrencyInfo>()
var currencyList = MutableLiveData<MutableList<CurrencyInfo>>()
fun getRecordsFromFixer() {
CoroutineScope(Dispatchers.IO).launch {
var listWithCurrencies: MutableList<CurrencyInfo>
val formatter = DateTimeFormatter.ISO_LOCAL_DATE
val formatted = day.format(formatter)
listWithCurrencies = repository.getRecordsFromApi(formatted)
for (i in listWithCurrencies) {
val formattedRate = formattingRates(i.currency, i.rate)
listWithFormattedCurrencies.add(CurrencyInfo(i.date, i.currency, formattedRate))
}
currencyList.postValue(listWithFormattedCurrencies)
day = day.minusDays(1)
}
}
private fun formattingRates(name : String, rate : String): String {
return if(name =="BTC" && name.isNotEmpty() && rate.isNotEmpty()){
String.format("%.8f", rate.toDouble())
}else if (name.isNotEmpty() && rate.isNotEmpty()){
String.format("%.4f", rate.toDouble())
}else{
rate
}
}
}
class CurrencyViewModelTest {
#ExperimentalCoroutinesApi
private val testDispatcher = StandardTestDispatcher()
#get:Rule
val rule = InstantTaskExecutorRule()
private lateinit var viewModel: CurrencyViewModel
#Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
}
#After
fun tearDown(){
Dispatchers.resetMain()
}
#Test
fun `formatting empty name returns empty rate` () = runBlocking{
val currencyRepository = mock(currencyRepository::class.java)
val viewModel = CurrencyViewModel(currencyRepository)
val date = "2022-02-02"
val listOfCurrencies = mutableListOf(CurrencyInfo(date,"", "0.222"))
Mockito.`when` (currencyRepository.getRecordsFromApi(date)).thenReturn(listOfCurrencies)
viewModel.getRecordsFromFixer()
assertThat(viewModel.currencyList.getOrAwaitValue().contains(CurrencyInfo(date,"",""))).isTrue()
}
}
Error
Exception in thread "DefaultDispatcher-worker-1 #coroutine#2" java.lang.NullPointerException
.
.
.
LiveData value was never set.
java.util.concurrent.TimeoutException: LiveData value was never set.
The problem is that currencyRepository.getRecordsFromApi(date)) returns null object.
I tried to check if currencyRepository.getRecordsFromApi(date)) returns proper data after initializing it apart from ViewModel function and it works fine.
Why currencyRepository.getRecordsFromApi(date)) returns null when it is invoked in ViewModel function?
I finally found answer. I used Mockk library and rebulid my ViewModel.
class CurrencyViewModel(
private val repository: Repository
) : ViewModel() {
private var day: LocalDate = LocalDate.now()
private var formattedDate = getDate(day)
var listWithFormattedCurrencies = mutableListOf<CurrencyInfo>()
var currencyList = MutableLiveData<MutableList<CurrencyInfo>>()
var listWithCurrencies = mutableListOf<CurrencyInfo>()
fun getRecordsFromFixer() {
CoroutineScope(Dispatchers.IO).launch {
listWithCurrencies = getRecords()
for (i in listWithCurrencies) {
val formattedRate = formattingRates(i.currency, i.rate)
listWithFormattedCurrencies.add(CurrencyInfo(i.date, i.currency, formattedRate))
}
currencyList.postValue(listWithFormattedCurrencies)
day = day.minusDays(1)
formattedDate = getDate(day)
}
}
private fun formattingRates(name : String, rate : String): String {
return if(name =="BTC" && name.isNotEmpty() && rate.isNotEmpty()){
String.format("%.8f", rate.toDouble())
}else if (name.isNotEmpty() && rate.isNotEmpty()){
String.format("%.4f", rate.toDouble())
}else{
rate
}
}
private suspend fun getRecords() : MutableList<CurrencyInfo>{
return repository.getRecordsFromApi(formattedDate)
}
private fun getDate(day : LocalDate) : String{
val formatter = DateTimeFormatter.ISO_LOCAL_DATE
return day.format(formatter)
}
}
class CurrencyViewModelTest {
private lateinit var repository: currencyRepository
private lateinit var viewModel: CurrencyViewModel
#ExperimentalCoroutinesApi
private val testDispatcher = StandardTestDispatcher()
#get:Rule
val rule = InstantTaskExecutorRule()
#Before
fun setUp() {
repository = currencyRepository()
viewModel = spyk(CurrencyViewModel(repository), recordPrivateCalls = true)
Dispatchers.setMain(testDispatcher)
}
#After
fun tearDown(){
Dispatchers.resetMain()
}
#Test
fun `formatting empty name and rate returns empty name and rate` () = runBlocking{
val date = "2022-02-02"
val listOfCurrencies = mutableListOf(CurrencyInfo(date,"", ""))
coEvery{ viewModel["getRecords"]()} returns listOfCurrencies
coEvery { viewModel.getRecordsFromFixer() } answers {callOriginal()}
viewModel.getRecordsFromFixer()
assertEquals(mutableListOf(CurrencyInfo(date,"", "")), viewModel.currencyList.getOrAwaitValue())
}
#Test
fun `receiving proper formatted data` () = runBlocking{
val date = "2022-02-02"
val listOfCurrencies = mutableListOf(CurrencyInfo(date,"USD", "0.4567234124"))
coEvery{ viewModel["getRecords"]()} returns listOfCurrencies
coEvery { viewModel.getRecordsFromFixer() } answers {callOriginal()}
viewModel.getRecordsFromFixer()
assertEquals(mutableListOf(CurrencyInfo(date,"USD", "0,4567")), viewModel.currencyList.getOrAwaitValue())
}
#Test
fun `receiving proper formatted BTC rate` () = runBlocking{
val date = "2022-02-02"
val listOfCurrencies = mutableListOf(CurrencyInfo(date,"BTC", "0.45672341241412345435654367645"))
coEvery{ viewModel["getRecords"]()} returns listOfCurrencies
coEvery { viewModel.getRecordsFromFixer() } answers {callOriginal()}
viewModel.getRecordsFromFixer()
assertEquals(mutableListOf(CurrencyInfo(date,"BTC", "0,45672341")), viewModel.currencyList.getOrAwaitValue())
}
I am learning Android development, and as I saw in many topics, people were talking about that LiveData is not recommended to use anymore. I mean it's not up-to-date, and we should use Flows instead.
I am trying to get data from ROOM database with Flows and then convert them to StateFlow because as I know they are observables, and I also want to add UI states to them. Like when I get data successfully, state would change to Success or if it fails, it changes to Error.
I have a simple app for practicing. It stores subscribers with name and email, and show them in a recyclerview.
I've checked a lot of sites, how to use stateIn method, how to use StateFlows and Flows but didn't succeed. What's the most optimal way to do this?
And also what's the proper way of updating recyclerview adapter? Is it okay to change it all the time in MainActivity to a new adapter?
Here is the project (SubscriberViewModel.kt - line 30):
Project link
If I am doing other stuff wrong, please tell me, I want to learn. I appreciate any kind of help.
DAO:
import androidx.room.*
import kotlinx.coroutines.flow.Flow
#Dao
interface SubscriberDAO {
#Insert
suspend fun insertSubscriber(subscriber : Subscriber) : Long
#Update
suspend fun updateSubscriber(subscriber: Subscriber) : Int
#Delete
suspend fun deleteSubscriber(subscriber: Subscriber) : Int
#Query("DELETE FROM subscriber_data_table")
suspend fun deleteAll() : Int
#Query("SELECT * FROM subscriber_data_table")
fun getAllSubscribers() : Flow<List<Subscriber>>
#Query("SELECT * FROM subscriber_data_table WHERE :id=subscriber_id")
fun getSubscriberById(id : Int) : Flow<Subscriber>
}
ViewModel:
class SubscriberViewModel(private val repository: SubscriberRepository) : ViewModel() {
private var isUpdateOrDelete = false
private lateinit var subscriberToUpdateOrDelete: Subscriber
val inputName = MutableStateFlow("")
val inputEmail = MutableStateFlow("")
private val _isDataAvailable = MutableStateFlow(false)
val isDataAvailable : StateFlow<Boolean>
get() = _isDataAvailable
val saveOrUpdateButtonText = MutableStateFlow("Save")
val deleteOrDeleteAllButtonText = MutableStateFlow("Delete all")
/*
//TODO - How to implement this as StateFlow<SubscriberListUiState> ??
//private val _subscribers : MutableStateFlow<SubscriberListUiState>
//val subscribers : StateFlow<SubscriberListUiState>
get() = _subscribers
*/
private fun clearInput() {
inputName.value = ""
inputEmail.value = ""
isUpdateOrDelete = false
saveOrUpdateButtonText.value = "Save"
deleteOrDeleteAllButtonText.value = "Delete all"
}
fun initUpdateAndDelete(subscriber: Subscriber) {
inputName.value = subscriber.name
inputEmail.value = subscriber.email
isUpdateOrDelete = true
subscriberToUpdateOrDelete = subscriber
saveOrUpdateButtonText.value = "Update"
deleteOrDeleteAllButtonText.value = "Delete"
}
fun saveOrUpdate() {
if (isUpdateOrDelete) {
subscriberToUpdateOrDelete.name = inputName.value
subscriberToUpdateOrDelete.email = inputEmail.value
update(subscriberToUpdateOrDelete)
} else {
val name = inputName.value
val email = inputEmail.value
if (name.isNotBlank() && email.isNotBlank()) {
insert(Subscriber(0, name, email))
}
inputName.value = ""
inputEmail.value = ""
}
}
fun deleteOrDeleteAll() {
if (isUpdateOrDelete) {
delete(subscriberToUpdateOrDelete)
} else {
deleteAll()
}
}
private fun insert(subscriber: Subscriber) = viewModelScope.launch(Dispatchers.IO) {
repository.insert(subscriber)
_isDataAvailable.value = true
}
private fun update(subscriber: Subscriber) = viewModelScope.launch(Dispatchers.IO) {
repository.update(subscriber)
clearInput()
}
private fun delete(subscriber: Subscriber) = viewModelScope.launch(Dispatchers.IO) {
repository.delete(subscriber)
clearInput()
}
private fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
repository.deleteAll()
//_subscribers.value = SubscriberListUiState.Success(emptyList())
_isDataAvailable.value = false
}
sealed class SubscriberListUiState {
data class Success(val list : List<Subscriber>) : SubscriberListUiState()
data class Error(val msg : String) : SubscriberListUiState()
}
}
MainActivity:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: SubscriberViewModel
private lateinit var viewModelFactory: SubscriberViewModelFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val dao = SubscriberDatabase.getInstance(application).subscriberDAO
viewModelFactory = SubscriberViewModelFactory(SubscriberRepository(dao))
viewModel = ViewModelProvider(this, viewModelFactory)[SubscriberViewModel::class.java]
binding.viewModel = viewModel
binding.lifecycleOwner = this
initRecycleView()
}
private fun initRecycleView() {
binding.recyclerViewSubscribers.layoutManager = LinearLayoutManager(
this#MainActivity,
LinearLayoutManager.VERTICAL, false
)
displaySubscribersList()
}
private fun displaySubscribersList() {
/*
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.subscribers.collect { uiState ->
when (uiState) {
is SubscriberViewModel.SubscriberListUiState.Success -> {
binding.recyclerViewSubscribers.adapter = SubscriberRecyclerViewAdapter(uiState.list) {
subscriber: Subscriber -> listItemClicked(subscriber)
}
}
is SubscriberViewModel.SubscriberListUiState.Error -> {
Toast.makeText(applicationContext,uiState.msg, Toast.LENGTH_LONG).show()
}
}
}
}
}*/
}
private fun listItemClicked(subscriber: Subscriber) {
Toast.makeText(this, "${subscriber.name} is selected", Toast.LENGTH_SHORT).show()
viewModel.initUpdateAndDelete(subscriber)
}
}
You can convert a Flow type into a StateFlow by using stateIn method.
private val coroutineScope = CoroutineScope(Job())
private val flow: Flow<CustomType>
val stateFlow = flow.stateIn(scope = coroutineScope)
In order to transform the CustomType into UIState, you can use the transformLatest method on Flow. It will be something like below:
stateFlow.transformLatest { customType ->
customType.toUiState()
}
Where you can create an extension function to convert CustomType to UiState like this:
fun CustomType.toUiState() = UiState(
x = x,
y = y... and so on.
)
I'm Testing app with clean architecture, koin, mockito. but it shows nullpointer exception. I searched a lot but couldn't get solution.. Please help me...
here is my codes. I'm making ToDo app with clean architecture.
internal class ListViewModelTest:ViewModelTest() {
private val viewModel: ListViewModel by inject ()
private val insertToDoListUseCase: InsertToDoListUseCase by inject()
private val getToDoItemUseCase: GetToDoItemUseCase by inject()
private val mockList= (0 until 10).map {
ToDoEntity(
id = it.toLong(),
title = "title $it",
description = "description $it",
hasCompleted = false)
}
#Before
fun init(){
initData()
}
private fun initData() = runBlockingTest {
insertToDoListUseCase(mockList)
}
#Test
fun `test viewModel fetch`():Unit = runBlockingTest {
val testObservable = viewModel.todoListLiveData.test()
viewModel.fetchData()
testObservable.assertValueSequence(
listOf(
mockList
)
)
}
#Test
fun `test item Update`(): Unit = runBlockingTest {
val todo = ToDoEntity(
id = 1,
title = "title 1",
description = "description 1",
hasCompleted = true
)
viewModel.updateEntity(todo)
assert(getToDoItemUseCase(todo.id)?.hasCompleted ?: false == todo.hasCompleted)
}
}
this is my ViewModelTest
#ExperimentalCoroutinesApi
internal abstract class ViewModelTest: KoinTest {
#get:Rule
val mockitoRule: MockitoRule = MockitoJUnit.rule()
#Mock
private lateinit var context: Application
private val dispatcher = TestCoroutineDispatcher()
#Before
fun setup(){
startKoin{
androidContext(context)
modules(appTestModule)
}
Dispatchers.setMain(dispatcher)
}
#After
fun tearDown(){
stopKoin()
Dispatchers.resetMain()
}
protected fun <T> LiveData<T>.test(): LiveDataTestObserver<T>{
val testObserver = LiveDataTestObserver<T>()
observeForever(testObserver)
return testObserver
}
}
first test is having nullpointerexception.. whats the problem?
I think
private lateinit var context: Application
make problem so set initial value of context
I'm trying to write a test for my view model that gets the data from datastore but I can't figure it out. this is my data store implementation. I save the user data such as email and token when user is sign up or sign in :
class FlowAuthenticationDataStore(context: Context) : AuthenticationDataStore {
private val dataStore = context.createDataStore(name = "user_auth")
private val userEmailKey = preferencesKey<String>(name = "USER_EMAIL")
private val userTokenKey = preferencesKey<String>(name = "USER_TOKEN")
private val userNameKey = preferencesKey<String>(name = "USER_NAME")
private val userIsLoginKey = preferencesKey<Boolean>(name = "USER_IS_LOGIN")
override suspend fun updateUser(userDataStore: UserDataStoreModel) {
dataStore.edit {
it[userEmailKey] = userDataStore.email
it[userTokenKey] = userDataStore.token
it[userNameKey] = userDataStore.name
it[userIsLoginKey] = userDataStore.isLogin
}
}
override fun observeUser(): Flow<UserDataStoreModel> {
return dataStore.data.catch {
if (it is IOException) {
emit(emptyPreferences())
} else {
throw it
}
}.map {
UserDataStoreModel(
it[userIsLoginKey]!!,
it[userNameKey]!!,
it[userEmailKey]!!,
it[userTokenKey]!!
)
}
}
}
and this is my view model. I observe the user data store and if its success and has a data then I update my live data. If there is not data and user is first time to register then my live data is equal to default value from data class :
class SplashScreenViewModel(
private val flowOnBoardingDataStore: FlowAuthenticationDataStore,
private val contextProvider: CoroutineContextProvider,
) :
ViewModel() {
private val _submitState = MutableLiveData<UserDataStoreModel>()
val submitState: LiveData<UserDataStoreModel> = _submitState
fun checkUserLogin() {
viewModelScope.launch {
kotlin.runCatching {
withContext(contextProvider.io) {
flowOnBoardingDataStore.observeUser().collect {
_submitState.value = it
}
}
}.onFailure {
_submitState.value = UserDataStoreModel()
}
}
}
}
and this is my test class:
#ExperimentalCoroutinesApi
class SplashScreenViewModelTest {
private val dispatcher = TestCoroutineDispatcher()
#get:Rule
val rule = InstantTaskExecutorRule()
#get:Rule
val coroutineTestRule = CoroutineTestRule(dispatcher)
#RelaxedMockK
lateinit var flowOnBoardingDataStore: FlowAuthenticationDataStore
private fun createViewModel()=SplashScreenViewModel(flowOnBoardingDataStore,
CoroutineContextProvider(dispatcher,dispatcher)
)
#Before
fun setup() {
MockKAnnotations.init(this)
}
#After
fun tearDown() {
unmockkAll()
}
#Test
fun `when user is already sign in, then state should return model`()=dispatcher.runBlockingTest {
val viewModel=createViewModel()
val userDataStoreModel= UserDataStoreModel(true,"test","test","test")
flowOnBoardingDataStore.updateUser(userDataStoreModel)
viewModel.checkUserLogin()
assertEquals(userDataStoreModel,viewModel.submitState.value)
}
}
This is the result of my test function:
junit.framework.AssertionFailedError:
Expected :UserDataStoreModel(isLogin=true, name=test, email=test, token=test)
Actual :null
I find the solution and I posted here Incase anybody needs it.
The solution is using coEvery to return a fake data with flowOf from the usecase( you don't need to use flowOf , its based on your return data from your use case, in my case it's return a flow):
#Test
fun `when user is already sign in, then state should return user data`()=dispatcher.runBlockingTest {
val userData=UserDataStoreModel(true, Name,
Email,"","")
coEvery { authenticationDataStore.observeUser() }returns flowOf(userData)
val viewModel=createViewModel()
viewModel.checkUserLogin()
assertEquals(userData,viewModel.submitState.value)
}
This is the full test class:
#ExperimentalCoroutinesApi
class SplashScreenViewModelTest {
private val dispatcher = TestCoroutineDispatcher()
#get:Rule
val rule = InstantTaskExecutorRule()
#get:Rule
val coroutineTestRule = CoroutineTestRule(dispatcher)
#RelaxedMockK
lateinit var authenticationDataStore: AuthenticationDataStore
private fun createViewModel()=SplashScreenViewModel(authenticationDataStore,
CoroutineContextProvider(dispatcher,dispatcher)
)
#Before
fun setup() {
MockKAnnotations.init(this)
}
#After
fun tearDown() {
unmockkAll()
}
#Test
fun `when user is already sign in, then state should return user data`()=dispatcher.runBlockingTest {
val userData=UserDataStoreModel(true, Name,
Email,"","")
coEvery { authenticationDataStore.observeUser() }returns flowOf(userData)
val viewModel=createViewModel()
viewModel.checkUserLogin()
assertEquals(userData,viewModel.submitState.value)
}
}