So I have the following test that keeps failing with the following error:
java.lang.AssertionError: No values (latch = 1, values = 0, errors = 0, completions = 0)
val ocrProcessor = mockk<FirebaseFormProcessor>()
val date = listOf(DateTextExtraction())
every { ocrProcessor.scan(any(), any(), any()) } answers {
thirdArg<OcrResultCallback>().invoke(date)
}
viewModel = FormViewModel(ocrProcessor)
viewModel.addImage(bitmap)
viewModel.ocrAlert
.test()
.assertValue {
it == date
}
.addTo(disposeBag)
What this tries to test is the following:
override val ocrAlert: PublishSubject<List<TextExtractionInterface>> = PublishSubject.create()
override fun addImage(bitmap: Bitmap) {
if (files.value.isEmpty())
ocrProcessor.scan(bitmap, extract = textExtractionItems) { ocrResult ->
ocrAlert.onNext(ocrResult)
}
}
I am not quite sure what I am doing wrong here but I think it might have to do with threading problems.
edit:
I changed the code to this now:
val toBeTested = viewModel.ocrAlert
.subscribeOn(scheduler)
.observeOn(scheduler)
.test()
viewModel.addImage(bitmap)
toBeTested
.assertValue {
it == date
}
.addTo(disposeBag)
Related
I'm doing a small project to learn flow and the latest Android features, and I'm currently facing the viewModel's testing, which I don't know if I'm performing correctly. can you help me with it?
Currently, I am using a use case to call the repository which calls a remote data source that gets from an API service a list of strings.
I have created a State to control the values in the view model:
data class StringItemsState(
val isLoading: Boolean = false,
val items: List<String> = emptyList(),
val error: String = ""
)
and the flow:
private val stringItemsState = StringtemsState()
private val _stateFlow = MutableStateFlow(stringItemsState)
val stateFlow = _stateFlow.asStateFlow()
and finally the method that performs all the logic in the viewModel:
fun fetchStringItems() {
try {
_stateFlow.value = stringItemsState.copy(isLoading = true)
viewModelScope.launch(Dispatchers.IO) {
val result = getStringItemsUseCase.execute()
if (result.isEmpty()) {
_stateFlow.value = stringItemsState
} else {
_stateFlow.value = stringItemsState.copy(items = result)
}
}
} catch (e: Exception) {
e.localizedMessage?.let {
_stateFlow.value = stringItemsState.copy(error = it)
}
}
}
I am trying to perform the test following the What / Where / Then pattern, but the result is always an empty list and the assert verification always fails:
private val stringItems = listOf<String>("A", "B", "C")
#Test
fun `get string items - not empty`() = runBlocking {
// What
coEvery {
useCase.execute()
} returns stringItems
// Where
viewModel.fetchStringItems()
// Then
assert(viewModel.stateFlow.value.items == stringItems)
coVerify(exactly = 1) { viewModel.fetchStringItems() }
}
Can someone help me and tell me if I am doing it correctly? Thanks.
I'm trying to create this MockController with mockk to avoid create a new class for testing.
Is possible to do that?
class MockController : IController {
override lateinit var output: (String) -> Unit
override fun start() {
output("OK")
}
}
Class to test:
class ClassToTest(
private val controller: IController,
private val output: (String) -> Unit
){
fun start() {
controller.output = { result ->
output(result)
}
controller.start()
}
}
Then I use like this TEST example:
#Test
fun checkOutputIsCalled() {
runBlocking {
var outputCalled = false
val outputClassToTest: (String) -> Unit = {
outputCalled = true
}
val classToTest = ClassToTest(MockController(), outputClassToTest)
classToTest.start()
delay(1000)
assert(outputCalled)
}
}
I'm trying to update:
#Test
fun checkOutputIsCalled() {
runBlocking {
val controller = spyk<IController>()
var outputCalled = false
val outputClassToTest: (String) -> Unit = {
outputCalled = true
}
val classToTest = ClassToTest(controller, outputClassToTest)
every { controller.start() } answers {
controller.output.invoke("OK")
} //When I execute the test, output is null because yet doesn't exist the output creted inside ClassToTest
classToTest.start()
delay(1000)
assert(outputCalled)
}
}
When I execute the test, output is null because yet doesn't exist the output creted inside ClassToTest
How this could be after the output assign?
Thanks!
You should mock your output object and your Controller. Once done, tell your mocked controller to return the mocked output when property is called. Right after the start() invocation you can verify that output lambda was invoked. Please note that all your mocks must be relaxed.
class ClassToTestTesting {
companion object {
const val INVOCATION_PARAM = "OK"
}
#Test
fun test() = runBlocking {
val paramOutput: (String) -> Unit = mockk(relaxed = true)
val controller: IController = mockk(relaxed = true) {
every { output } returns paramOutput
every { start() } answers { output.invoke(INVOCATION_PARAM) }
}
val classToTest = ClassToTest(
controller,
paramOutput
)
classToTest.start()
verify { paramOutput(INVOCATION_PARAM) }
}
}
Im currently trying to write test for my method that use shared flow object and get data from it. the problem is when in trying to set method return with mockito, shared flow not emit anything and after 1 minute test result is fails.
My test:
#ExperimentalCoroutinesApi
#Test
fun `when response body have error in request login`() = runBlockingTest {
runCurrent()
Mockito.`when`(webSocketClient.isConnect()).thenReturn(true)
Mockito.`when`(mapper.createRPC(userLoginObject)).thenReturn(rpc)
Mockito.`when`(requestManager.sendRequest(rpc)).thenReturn(userLoginFlow)
userLoginFlow.emit(errorObject)
loginServiceImpl.requestLogin(userLoginObject).drop(1).collectLatest {
assert(it == errorObject)
}
}
My method
override fun requestLogin(userLoginObject: BaseDomain): Flow<DataState<BaseDomain>> = flow {
emit(DataState.Loading(ProgressBarState.Loading))
if (webSocketClient.isConnect()) {
requestManager.sendRequest(mapper.createRPC(userLoginObject)!!)?.filterNotNull()?.collectLatest {
if (it is IG_RPC.Error) {
emit(DataState.Error(ErrorObject(it.major, it.minor, it.wait)))
} else if (it is IG_RPC.Res_User_Register) {
val userLoginObject = userLoginObject as UserLoginObject
emit(
DataState.Data(
UserLoginObject(
userName = it.userName,
phoneNumber = userLoginObject.phoneNumber,
userId = it.userId,
authorHash = it.authorHash,
regex = it.codeRegex,
resendCodeDelay = it.resendDelayTime
)
)
)
}
}
} else {
emit(DataState.Error(ErrorObject(-1, -1, 0)))
}
}
Error log:
This job has not completed yet
java.lang.IllegalStateException: This job has not completed yet
I know that there are a lot of posts "How to cancel Coroutines Scope" but I couldn't find the answer for my case.
I have an Array of objects that I want to send each of them to Server using Coroutines.
What I need is, if one of my requests returns error, canceling others.
Here is my code:
private fun sendDataToServer(function: () -> Unit) {
LiabilitiesWizardSessionManager.getLiabilityAddedDocuments().let { documents ->
if (documents.isEmpty().not()) {
CoroutineScope(Dispatchers.IO).launch {
documents.mapIndexed { index, docDetail ->
async {
val result = uploadFiles(docDetail)
}
}.map {
var result = it.await()
}
}
} else function.invoke()
}
}
Below is my uploadFiles() function:
private suspend fun uploadFiles(docDetail: DocDetail): ArchiveFileResponse? {
LiabilitiesWizardSessionManager.mCreateLiabilityModel.let { model ->
val file = File(docDetail.fullFilePath)
val crmCode = docDetail.docTypeCode
val desc = docDetail.docTypeDesc
val id = model.commitmentMember?.id
val idType = 1
val createArchiveFileModel = CreateArchiveFileModel(108, desc, id, idType).apply {
this.isLiability = true
this.adaSystem = 3
}
val result = mRepositoryControllerKotlin.uploadFile(file, createArchiveFileModel)
return when (result) {
is ResultWrapper.Success -> {
result.value
}
is ResultWrapper.GenericError -> {
null
}
is ResultWrapper.NetworkError -> {
null
}
}
}
}
I know, I'm missing something.
I have written a unit test, based on Spek Framework, for one of my use cases. Following is the use case code:
class InsertRemoteWidgetUseCase(
private val remoteConfigProviderImplement: RemoteConfigProviderImplement,
private val feedDataStore: FeedDataStoreImplement
) {
fun invoke(): ArrayList<FeedData> {
if (remoteConfigProviderImplement.getHomeWidgetVisible()) {
val cardData = CardData()
cardData.url = remoteConfigProviderImplement.getHomeWidgetURL()
val feedData = FeedData()
feedData.type = CardType.REMOTE_CONFIG_WIDGET
feedData.listData = arrayListOf(cardData)
feedDataStore.insertRemoteWidgetCard(feedData)
} else {
feedDataStore.removeRemoteWidgetCard()
}
return feedDataStore.getData()
}
}
Here is the test case implementation:
class InsertRemoteWidgetUseCaseTest : Spek({
lateinit var mockRemoteConfigProviderImplement: RemoteConfigProviderImplement
lateinit var mockFeedDataStoreImplement: FeedDataStoreImplement
lateinit var instance: InsertRemoteWidgetUseCase
val feedComponentData1: FeedComponentData = mockk(relaxed = true)
val feedComponentData2: FeedComponentData = mockk(relaxed = true)
var remoteWidgetUrl = ""
var remoteWidgetVisible = false
val feedDataList1 = arrayListOf(
FeedData(listItems = arrayListOf(
CardItem(articleData = ArticleData(title = "A0"))
)
),
FeedData(listItems = arrayListOf(
CardItem(articleData = ArticleData(title = "B0")),
CardItem(articleData = ArticleData(title = "B1")),
CardItem(articleData = ArticleData(title = "B2"))
))
)
val feedDataList2 = arrayListOf(FeedData())
val pagination1 = Pagination(current = 0, next = 1)
val pagination2 = Pagination(current = 1, next = 2)
beforeEachTest {
every{feedComponentData1.pagination} returns pagination1
every{feedComponentData1.feedList} returns feedDataList1
every{feedComponentData2.pagination} returns pagination2
every{feedComponentData2.feedList} returns feedDataList2
mockRemoteConfigProviderImplement = mockk(relaxed = true)
mockFeedDataStoreImplement = spyk(FeedDataStoreImplement())
instance = spyk(InsertRemoteWidgetUseCase(mockRemoteConfigProviderImplement, mockFeedDataStoreImplement))
}
describe("invoke()") {
context("getHomeWidgetVisible is true") {
it("FeedData with CardType.REMOTE_CONFIG_WIDGET is inserted") {
remoteWidgetUrl = "www.google.com"
remoteWidgetVisible = true
every { mockRemoteConfigProviderImplement.getHomeWidgetURL() } returns remoteWidgetUrl
every { mockRemoteConfigProviderImplement.getHomeWidgetVisible() } returns remoteWidgetVisible
mockFeedDataStoreImplement.update(feedComponentData1)
mockFeedDataStoreImplement.update(feedComponentData2)
val feedDataListBefore = mockFeedDataStoreImplement.getData()
val feedDataListAfter = instance.invoke()
assert(feedDataListAfter[0].type == CardType.REMOTE_CONFIG_WIDGET)
assert(feedDataListBefore.size == feedDataListAfter.size + 1)
}
}
context("getHomeWidgetVisible is false") {
it("FeedData with CardType.REMOTE_CONFIG_WIDGET is removed") {
remoteWidgetUrl = "www.google.com"
remoteWidgetVisible = false
every { mockRemoteConfigProviderImplement.getHomeWidgetURL() } returns remoteWidgetUrl
every { mockRemoteConfigProviderImplement.getHomeWidgetVisible() } returns remoteWidgetVisible
mockFeedDataStoreImplement.update(feedComponentData1)
mockFeedDataStoreImplement.update(feedComponentData2)
val feedData = FeedData()
feedData.type = CardType.REMOTE_CONFIG_WIDGET
mockFeedDataStoreImplement.insertRemoteWidgetCard(feedData)
val feedDataListBefore = mockFeedDataStoreImplement.getData()
val feedDataListAfter = instance.invoke()
assert(feedDataListAfter[0].type != CardType.REMOTE_CONFIG_WIDGET)
assert(feedDataListBefore[0].type == CardType.REMOTE_CONFIG_WIDGET)
assert(feedDataListBefore.size == feedDataListAfter.size - 1)
}
}
}
})
The test cases in question runs successfully in local system. But with Bitrise, both of them fails with following:
java.lang.AssertionError: Assertion failed
at sg.com.sph.appbt.views.bnavigation.home.InsertRemoteWidgetUseCaseTest$1$2$1$1.invoke(InsertRemoteWidgetUseCaseTest.kt:74)
at sg.com.sph.appbt.views.bnavigation.home.InsertRemoteWidgetUseCaseTest$1$2$1$1.invoke(InsertRemoteWidgetUseCaseTest.kt:17)
at org.spekframework.spek2.runtime.scope.TestScopeImpl.execute(Scopes.kt:94)
at org.spekframework.spek2.runtime.Executor$execute$$inlined$executeSafely$lambda$1$1.invokeSuspend(Executor.kt:52)
at ???(Coroutine boundary.?(?)
at kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:99)
at org.spekframework.spek2.runtime.ExecutorsKt$doRunBlocking$1.invokeSuspend(executors.kt:8)
Caused by: java.lang.AssertionError: Assertion failed
at sg.com.sph.appbt.views.bnavigation.home.InsertRemoteWidgetUseCaseTest$1$2$1$1.invoke(InsertRemoteWidgetUseCaseTest.kt:74)
at sg.com.sph.appbt.views.bnavigation.home.InsertRemoteWidgetUseCaseTest$1$2$1$1.invoke(InsertRemoteWidgetUseCaseTest.kt:17)
at org.spekframework.spek2.runtime.scope.TestScopeImpl.execute(Scopes.kt:94)
at org.spekframework.spek2.runtime.Executor$execute$$inlined$executeSafely$lambda$1$1.invokeSuspend(Executor.kt:52)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:740)
Problem Statement
This executes well at local system without errors but fails with Bitrise on cloud.
Is there any silver bullet solution for this local vs remote issue.
Could this be due to different environment, if yes then which all components's versions should be checked.