首页 > 解决方案 > Android 单元测试数据存储

问题描述

我正在尝试为我的视图模型编写一个从数据存储中获取数据的测试,但我无法弄清楚。这是我的数据存储实现。当用户注册或登录时,我会保存用户数据,例如电子邮件和令牌:

    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]!!
            )
        }
    }
}

这是我的视图模型。我观察用户数据存储,如果它成功并且有数据,那么我会更新我的实时数据。如果没有数据并且用户是第一次注册,那么我的实时数据等于数据类的默认值:

    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()
            }
        }

    }
}

这是我的测试课:

    @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)
    }
}

这是我的测试功能的结果:

    junit.framework.AssertionFailedError: 
Expected :UserDataStoreModel(isLogin=true, name=test, email=test, token=test)
Actual   :null

标签: androidkotlin

解决方案


我找到了解决方案,并在此处发布以防万一有人需要它。该解决方案用于从用例coEvery返回假数据flowOf(您不需要使用flowOf,它基于您从用例返回的数据,在我的情况下它是 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)
    }

这是完整的测试类:

    @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)
    }
}

推荐阅读