I’m unit testing my viewmodel, specifically onSubmit
class MainViewModel(private val selectResponseTypeUseCase: SelectResponseTypeUseCase,
private val model: LangMod,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
private var messages: List<Chat> = listOf(Chat())): ViewModel() {
private val _conversation = MutableStateFlow<ResultState<List<Chat>>>(
ResultState.Success.NonEmpty(messages)
)
val conversation: StateFlow<ResultState<List<Chat>>> = _conversation
fun onSubmit(outgoingMsg: String) {
viewModelScope.launch(dispatcher) {
messages = messages + Chat(outgoingMsg, true)
_conversation.value = ResultState.Success.NonEmpty(messages)
try {
when (selectResponseTypeUseCase(outgoingMsg)) {
SelectResponseTypeUseCase.Response.TEXT -> { replyWithText(outgoingMsg) }
SelectResponseTypeUseCase.Response.IMAGE -> { replyWithImage(outgoingMsg) }
SelectResponseTypeUseCase.Response.TRANSLATION -> { replyWithTranslation(outgoingMsg) }
}
} catch (ex: Exception) {
_conversation.value = ResultState.Failure(ex)
}
}
}
suspend fun replyWithText(outgoingMsg: String) {
messages = messages + Chat(getTextReply(outgoingMsg), false)
_conversation.value = ResultState.Success.NonEmpty(messages)
}
suspend fun getTextReply(userMsg: String): String {
val reply = model.converse(userMsg)
delay(500)
return reply
}
My unit test is
@ExperimentalCoroutinesApi
class MainViewModelTest {
private val useCase = mockk<SelectResponseTypeUseCase>(relaxed=true)
private val model = mockk<LangMod>(relaxed = true)
private lateinit var vm: MainViewModel
@Before
fun setup() {
vm = MainViewModel(useCase, model)
Dispatchers.setMain(StandardTestDispatcher())
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
@Test
fun `asking a regular question results in a text reply`() = runTest {
val outgoingMsg = "What is your favorite sport?"
coEvery { useCase.invoke(outgoingMsg) } returns SelectResponseTypeUseCase.Response.TEXT
coEvery { model.converse(outgoingMsg) } returns "Basketball"
vm.onSubmit(outgoingMsg)
advanceTimeBy(1000)
coVerify { useCase.invoke(outgoingMsg) }
coVerify { model.converse(outgoingMsg) }
val expectedReply = Chat(message = "Basketball", fromUser = false)
val actualState = vm.conversation.value as ResultState.Success.NonEmpty
val actualReply = actualState.value.last()
assertEquals(expectedReply.message, actualReply.message)
}
}
If I remove the assertEquals the test passes, indicating “model.converse(outgoingMsg)” is called.
The sequence of events should be
- onSubmit receives “What is your favorite sport?”
- useCase is mocked to recognize it as a TEXT response, so it calls replyWithText(“What is your favorite sport?”)
- replyWithText(“What is your favorite sport?”) calls getTextReply(“What is your favorite sport?”)
- getTextReply(“What is your favorite sport?”) is mocked to return “Basketball” after 500 ms.
- replyWithText(“What is your favorite sport?”) appends a Chat containing “Basketball” to a new list of Chats that is assigned to the conversation stateflow.
But the test thinks the last message is “What is your favorite sport?”) instead of “Basketball”.
New contributor
Alex is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.