getting 'Missing calls inside every { ... } block' when using mokk - android

I'm using MockK as mock library for kotlin, for some reason I get an error on the every{} line.
io.mockk.MockKException: Missing calls inside every { ... } block.
this is my test class
internal class TestClass {
private val service: MyService = mockk(relaxed = true)
private val dao: MyDao = mockk(relaxed = true)
private val repository: MyRepository = MyRepositoryImp(
service = service,
dao = dao,
)
#Test
fun test() = runBlocking {
val expected = SOMETHING
every { repository.getValues() } returns flowOf(SOMETHINGELSE) // crash
assertEquals(1, 1)
}
}
MyService is just a retrofit interface
internal interface MyService {
#GET("123")
suspend fun getRemoteData(): Response<SOMECLASS>
}
Anyone has any idea? thanks!

You've instantiated the repository as a real instance. That means that it'll actually execute the code inside its implementation and that's known as the 'subject under test'.
But every { ... } returns ... needs a mockk instance.
So in your example, it should be
every { service.getRemoteData() } returns ...
or
every { dao.someMethod() } returns ...

Related

Passing errors coming from the API call

I am using 2 separate liveData exposed to show the error coming from the API. I am basically checking if there is an exception with the API call, pass a failure status and serverErrorLiveData will be observed.
So I have serverErrorLiveData for error and creditReportLiveData for result without an error.
I think I am not doing this the right way. Could you please guide me on what is the right way of catching error from the API call. Also, any concerns/recommendation on passing data from repository on to view model.
What is the right way of handing loading state?
CreditScoreFragment
private fun initViewModel() {
viewModel.getCreditReportObserver().observe(viewLifecycleOwner, Observer<CreditReport> {
showScoreUI(true)
binding.score.text = it.creditReportInfo.score.toString()
binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
initDonutView(
it.creditReportInfo.score.toFloat(),
it.creditReportInfo.maxScoreValue.toFloat()
)
})
viewModel.getServerErrorLiveDataObserver().observe(viewLifecycleOwner, Observer<Boolean> {
if (it) {
showScoreUI(false)
showToastMessage()
}
})
viewModel.getCreditReport()
}
MainActivityViewModel
class MainActivityViewModel #Inject constructor(
private val dataRepository: DataRepository
) : ViewModel() {
var creditReportLiveData: MutableLiveData<CreditReport>
var serverErrorLiveData: MutableLiveData<Boolean>
init {
creditReportLiveData = MutableLiveData()
serverErrorLiveData = MutableLiveData()
}
fun getCreditReportObserver(): MutableLiveData<CreditReport> {
return creditReportLiveData
}
fun getServerErrorLiveDataObserver(): MutableLiveData<Boolean> {
return serverErrorLiveData
}
fun getCreditReport() {
viewModelScope.launch(Dispatchers.IO) {
val response = dataRepository.getCreditReport()
when(response.status) {
CreditReportResponse.Status.SUCCESS -> creditReportLiveData.postValue(response.creditReport)
CreditReportResponse.Status.FAILURE -> serverErrorLiveData.postValue(true)
}
}
}
}
DataRepository
class DataRepository #Inject constructor(
private val apiServiceInterface: ApiServiceInterface
) {
suspend fun getCreditReport(): CreditReportResponse {
return try {
val creditReport = apiServiceInterface.getDataFromApi()
CreditReportResponse(creditReport, CreditReportResponse.Status.SUCCESS)
} catch (e: Exception) {
CreditReportResponse(null, CreditReportResponse.Status.FAILURE)
}
}
}
ApiServiceInterface
interface ApiServiceInterface {
#GET("endpoint.json")
suspend fun getDataFromApi(): CreditReport
}
CreditScoreResponse
data class CreditReportResponse constructor(val creditReport: CreditReport?, val status: Status) {
enum class Status {
SUCCESS, FAILURE
}
}
It's creates complexity and increased chances for a coding error to have two LiveData channels for success and failure. You should have a single LiveData that can offer up the data or an error so you know it's coming in orderly and you can observe it in one place. Then if you add a retry policy, for example, you won't risk somehow showing an error after a valid value comes in. Kotlin can facilitate this in a type-safe way using a sealed class. But you're already using a wrapper class for success and failure. I think you can go to the source and simplify it. You can even just use Kotlin's own Result class.
(Side note, your getCreditReportObserver() and getServerErrorLiveDataObserver() functions are entirely redundant because they simply return the same thing as a property. You don't need getter functions in Kotlin because properties basically are getter functions, with the exception of suspend getter functions because Kotlin doesn't support suspend properties.)
So, to do this, eliminate your CreditReportResponse class. Change your repo function to:
suspend fun getCreditReport(): Result<CreditReport> = runCatching {
apiServiceInterface.getDataFromApi()
}
If you must use LiveData (I think it's simpler not to for a single retrieved value, see below), your ViewModel can look like:
class MainActivityViewModel #Inject constructor(
private val dataRepository: DataRepository
) : ViewModel() {
val _creditReportLiveData = MutableLiveData<Result<CreditReport>>()
val creditReportLiveData: LiveData<Result<CreditReport>> = _creditReportLiveData
fun fetchCreditReport() { // I changed the name because "get" implies a return value
// but personally I would change this to an init block so it just starts automatically
// without the Fragment having to manually call it.
viewModelScope.launch { // no need to specify dispatcher to call suspend function
_creditReportLiveData.value = dataRepository.getCreditReport()
}
}
}
Then in your fragment:
private fun initViewModel() {
viewModel.creditReportLiveData.observe(viewLifecycleOwner) { result ->
result.onSuccess {
showScoreUI(true)
binding.score.text = it.creditReportInfo.score.toString()
binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
initDonutView(
it.creditReportInfo.score.toFloat(),
it.creditReportInfo.maxScoreValue.toFloat()
)
}.onFailure {
showScoreUI(false)
showToastMessage()
}
viewModel.fetchCreditReport()
}
Edit: the below would simplify your current code, but closes you off from being able to easily add a retry policy on failure. It might make better sense to keep the LiveData.
Since you are only retrieving a single value, it would be more concise to expose a suspend function instead of LiveData. You can privately use a Deferred so the fetch doesn't have to be repeated if the screen rotates (the result will still arrive and be cached in the ViewModel). So I would do:
class MainActivityViewModel #Inject constructor(
private val dataRepository: DataRepository
) : ViewModel() {
private creditReportDeferred = viewModelScope.async { dataRepository.getCreditReport() }
suspend fun getCreditReport() = creditReportDeferred.await()
}
// In fragment:
private fun initViewModel() = lifecycleScope.launch {
viewModel.getCreditReport()
.onSuccess {
showScoreUI(true)
binding.score.text = it.creditReportInfo.score.toString()
binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
initDonutView(
it.creditReportInfo.score.toFloat(),
it.creditReportInfo.maxScoreValue.toFloat()
)
}.onFailure {
showScoreUI(false)
showToastMessage()
}
}

Why API actually called when run unit test of a function used Coroutine to call API with networkBoundResource in Android?

I have an issue in unit test for a function used Coroutine to call API with networkBoundResource.
The issue is when run the test the API actually called, although it's supposed to return the expected response such as I determined in this line: whenever(mfSDKPaymentRepository.sendPayment(request)).thenReturn(expectedResponse)
This is the function want to test:
fun callSendPayment(
coroutineScope: CoroutineScope? = GlobalScope,
request: MFSendPaymentRequest,
apiLang: String,
listener: (MFResult<MFSendPaymentResponse>) -> Unit
) {
Const.apiLang = apiLang
coroutineScope?.launch(Dispatchers.Main) {
val dataResource = networkBoundResource {
mInteractors.sendPayment(request)
}
when (dataResource) {
is MFResult.Success ->
listener.invoke(MFResult.Success(dataResource.value.response!!))
is MFResult.Fail ->
listener.invoke(MFResult.Fail(dataResource.error))
}
}
}
This is the test class:
class MFSDKMainTest {
private val mfSDKPaymentRepository = mock<MFSDKPaymentGateWay>()
private val testScope = TestCoroutineScope()
#get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
#Before
fun setup() {
Dispatchers.setMain(Dispatchers.Unconfined)
}
#After
fun tearDown() {
Dispatchers.resetMain()
testScope.cleanupTestCoroutines()
}
#Test
fun testCallSendPayment() = runBlockingTest {
val data = MFSendPaymentResponse(invoiceId = ID)
val expectedResponse = SDKSendPaymentResponse(data)
val request = MFSendPaymentRequest(
0.100,
"Customer name",
MFNotificationOption.LINK
)
val lang = MFAPILanguage.EN
whenever(mfSDKPaymentRepository.sendPayment(request))
.thenReturn(expectedResponse)
MFSDKMain.callSendPayment(testScope, request, lang) {
assert(it is MFResult.Success)
}
}
}
In your function when you call
mInteractors.sendPayment(request)
how do you get a refrence to MFSDKPaymentGateWay? In your test method you are not setting the mocked object in MFSDKMain class.
If you are creating it inside the same class (which i assume is an Object class) you may need to find a way to mock object class. Probably you need to use mockk instead of mockito
If you have setter method for MFSDKPaymentGateWay you should call it in your test method.

How to mock android room withTransaction method with Mockk

I'm trying to make some unit tests for my business logic.
I have repository in which I save to room database (2.1.0-rc01) some data from response.
Data saving into different tables with different dao in single transaction.
Code is simplified:
ItemRepository
suspend fun saveItems(response: Response) {
val items = response.items.map { it.toLocalItem() }
val subItems = response.items.flatMap { item ->
item.subItems.map { it.toLocal(item.id) }
}
db.withTransaction {
db.itemDao().deleteAll()
db.itemDao().insertAll(items)
db.subItemDao().insertAll(subItems)
}
}
For unit test I'm using Mockk library. How can I mock room withTransaction method?. withTransaction is declared as
suspend fun <R> RoomDatabase.withTransaction(block: suspend () -> R): R
I'm trying to writing test
#MockK
private lateinit var database: AppDatabase
#MockK
private lateinit var itemDao: ItemDao
#MockK
private lateinit var subItemDao: SubItemDao
#Test
fun checkSaveItems() = runBlocking {
repository = ItemRepository(database)
coEvery { database.itemDao() } returns itemDao
coEvery { database.subItemDao() } returns subItemDao
//TODO: execute database.withTransaction(block: suspend () -> R)
coEvery { itemDao.deleteAll() } just Runs
coEvery { itemDao.insertAll(any()) } just Runs
coEvery { subItemDao.insertAll(any()) } just Runs
repository.saveItems(testResponse)
coVerifySequence {
itemDao.deleteAll()
itemDao.insertAll(testItems)
subItemDao.insertAll(testSubItems)
}
}
You first have to enable static mocks for the Android Room KTX method withTransaction {}. You also need to capture the suspend lambda function passed to it. This captured function can just be invoked so the code inside it runs. Since you're mocking all the database calls, you don't need a real transaction here.
#Before
fun initMocks() {
MockKAnnotations.init(this)
mockkStatic(
"androidx.room.RoomDatabaseKt"
)
val transactionLambda = slot<suspend () -> R>()
coEvery { db.withTransaction(capture(transactionLambda)) } coAnswers {
transactionLambda.captured.invoke()
}
}
You should then be able to run your code as written.
To expand on Andrew's answer, the mockk documentation for extension functions shows that if you are mocking an object wide or class wide extension function, you can just use regular mockk to achieve that. However, if you are using a module wide extension function, like withTransaction, you also need to perform mockkStatic on the module's class name.

Mockito - `when` execute function instead mock it

I'm kind of new with mockito so I don't know if this behavior is normal or not.
This is a simple example class:
open class Example( val example2: Example2) {
fun getStuff(fileName: String) : String {
return example2.getFileExtension(fileName)
}
}
open class Example2(val fileUtils: FileUtils) {
fun getFileExtension(fileName: String): String {
return fileUtils.getExtension(fileName)
}
}
So when I tried to test it with this code:
class ExampleTest {
lateinit var example: Example
val example2 = mock(Example2::class.java)
#Before
fun init() {
example = Example(example2)
}
#Test
fun getFileExtensionTest() {
val resultExpected = "jpg"
`when`(example2.getFileExtension(ArgumentMatchers.anyString())).thenReturn("jpg")
assertThat(example.getStuff("hello.jpg"), `is`(resultExpected))
}
}
I'm getting a NullPointerException in return fileUtils.getExtension(fileName) when 'when' is executed
So 'when' is executing the function that suppose to be mocked.
I got it!!
Kotlin makes a function final by default and mockito can't mock final functions/classes. So there are two solutions here:
Add open to the functions
Or make mockito use the final classes and functions : Mockito 2 now supports final

Mock Lazy constructor parameter

it is my first question so apologies if I did something wrong.
I'm testing a usecase that takes an injected Lazy Dagger constructor parameter.
import dagger.Lazy
class TrackSpeed (val analyticsRepository: Lazy<AnalyticsRepository>) {
fun execute(timeMillis: Long) {
analyticsRepository.get().trackSpeed(timeMillis)
}
}
I don't know how to mock AnalyticsRepository as it is Lazy.
This is how my test class looks right now:
class TrackSpeedTest {
private lateinit var trackSpeed: TrackSpeed
private val analyticsRepository: Lazy<AnalyticsRepository> = mock()
#Before
fun setUp() {
trackSpeed = TrackSpeed(analyticsRepository)
}
#Test
fun testTrackSpeed() {
val time: Long = 0
trackSpeed.execute(time)
verify(analyticsRepository.get(), times(1))
.trackSpeed(time)
}
There are no compilation errors but when I run the test it fails with this exception:
java.lang.ClassCastException: io.reactivex.internal.operators.flowable.FlowableError cannot be cast to com.package.AnalyticsRepository at com.package.TrackSpeed.execute()
Any suggestions?
Thanks in advance.
You could create an actual instance of Lazy that returns your mocked AnalyticsRepository in get():
analyticsRepository = mock<AnalyticsRepository>()
val lazy = Lazy<AnalyticsRepository> { analyticsRepository }
trackSpeed = TrackSpeed(lazy)

Categories

Resources