I am trying to write test code for a ViewModel.
and the ViewModel uses this repository, so I'd like to mock it.
class ServiceRepository @Inject constructor(
private val service: MyService,
private val dao: MyDao
) {
suspend fetchMenus(filter: FilterData) = flow{
val insertList = mutableListOf<MenuData>()
val deleteList = mutableListOf<MenuData>()
val response = try {
decodeMenus(filter)
} catch(e: CustomIOException) {
// catch
}
val result = response.getOrNull()?.results?.map{ menu ->
menu.isRead = isRead(menu)
}
}
private suspend fun decodeMenus(): Result<MenuListData> {
return try {
val response = service.fetchMenus()
when{
response.isSuccessful -> Result.success(resonse.body()!!.data)
else -> Result.failure(CustomException())
}
}catch (e: Exception){
Result.failure(e)
}
}
private fun isRead(menu: MenuData): Boolean {
val ret = if(menu.status == MenuType.yesterday.index){
true
}else{
val checkedList = dao.getChecked(menu.key)
if(checkedList.isEmpty()){
false
}else{
checkedList.first().isChecked
}
}
return ret
}
}
And here's my test code that I am trying to write.
@ExperimentalCoroutinesApi
@ExtendWith(CoroutinesTestRule::class)
class MainViewModelTest {
private val instantExecutorRule = InstantTaskExecutorRule()
private val mockWebServer = MockWebServer()
@get:Rule
val rule: RuleChain = RuleChain
.outerRule(instantExecutorRule)
.around(mockWebServer)
private val retrofit by lazy {
val client = OkHttpClient.Builder().apply {
connectTimeout(5, TimeUnit.SECONDS)
callTimeout(5, TimeUnit.SECONDS)
readTimeout(5, TimeUnit.SECONDS)
writeTimeout(5, TimeUnit.SECONDS)
retryOnConnectionFailure(true)
}
val gson = GsonBuilder()
.setLenient()
.setDateFormat("yyyy-MM-dd'T'hh:mm:ssZ")
.create()
Retrofit.Builder()
.baseUrl(mockWebServer.url("/"))
.client(client.build())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addConverterFactory(NullOnEmptyConverterFactory())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
private val mService by lazy {
retrofit.create(MyService::class.java)
}
@MockK
private lateinit var mDao: MyDao
// no mock
private lateinit var serviceRepo: ServiceRepository
// should mock?
private lateinit var mainViewModel: MainViewModel
@Before
fun setUp() {
Dispatchers.setMain(Dispatchers.Unconfined)
MockKAnnotations.init(this, relaxed = true)
serviceRepo = ServiceRepository(mService, mDao)
mainViewModel = MainViewModel(serviceRepo, userRepo, prefRepo)
}
@Test
fun loadMenuList_returnMenuList(){
val fakeResJson = loadJson("/example_success.json")
val fakeFilter = FilterData("sunday")
val mockResponseBody = MockResponse().setBody(fakeResJson).setResponsCode(200)
val mockWebServer.enqueue(mockResponseBody)
val responseBody = mService.fetchMenus(FilterData("yesterday"))
coEvery { mService.fetchMenus("yesterday") } return responseBody
// coEvery { serviceRepo.fetchMenus("yesterday") } return ???
}
Since I need to get response from api, I used MockWebServer and created responseBody
. And then I mock mService to return responseBody
when fetchMenus()
is called.
this is what I tired in other way:
@Mockk
private lateinit var mService: MyService
// TODO: without MockWebServer
@Test
fun loadReceptionList2_returnReceptionList() = runTest {
// given
coEvery {
mService.fetchMenus("yesterday")
} returns // How can I create return value...?
}
But I don't know how to create the return value there.
So, when serviceRepo calls fetchMenus("yesterday"), it must be called. However, since ServiceRepository is very complicated and it has flow. I don't know how to achieve this. How can I do that?