1

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?

c-an
  • 3,543
  • 5
  • 35
  • 82

0 Answers0