How to mock "remove" extension of shared preferences using mockk? - android

Problem Description
I have a method of a data source class that makes the following call to a Shared Preferences extension:
override suspend fun removePendingBatch() {
preferences.remove(pendingBatchKey)
}
preferences is an instance of Shared Preferences
I need to do an unit test using mockk that checks if when calling this method of my data source class the call to this shared preferences extension is made.
In my test scenario I instantiate my preferences object like this:
private val preferences: SharedPreferences = mockk()
This is my unit test:
#Test
fun `when removePendingBatch() is called then remove from preferences`() = runBlockingTest {
every { preferences.remove(PENDING_BATCH_KEY) } just runs
batchDataSource.removePendingBatch()
verify(exactly = 1) { preferences.remove(PENDING_BATCH_KEY) }
}
The problem happens when I try to run this unit test, a mockk error saying there is no mock response is thrown:
no answer found for: Editor(child of #1#3).commit()
io.mockk.MockKException: no answer found for: Editor(child of #1#3).commit()
at io.mockk.impl.stub.MockKStub.defaultAnswer(MockKStub.kt:90)
at io.mockk.impl.stub.MockKStub.answer(MockKStub.kt:42)
at io.mockk.impl.recording.states.AnsweringState.call(AnsweringState.kt:16)
at io.mockk.impl.recording.CommonCallRecorder.call(CommonCallRecorder.kt:53)
at io.mockk.impl.stub.MockKStub.handleInvocation(MockKStub.kt:263)
...
My attempt fails
I imagine that I should mock the editor method call inside the shared preferences, however it seems to me that the mockk can't do that very well with the just runs statement, because when trying to do it the following syntax error appears:

I managed to solve this problem by mocking the commit method coming from the SharedPreferences.Editor object by this way:
every { preferences.edit().commit() } returns RandomUtils.nextBoolean()
In this case, just return any boolean, so we can mock the "remove" method, my complete test looks like this:
#Test
fun `when removePendingBatch() is called then remove from preferences`() = runBlockingTest {
every { preferences.edit().commit() } returns RandomUtils.nextBoolean()
every { preferences.remove(PENDING_BATCH_KEY) } just runs
batchDataSource.removePendingBatch()
verify(exactly = 1) { preferences.remove(PENDING_BATCH_KEY) }
}

Related

Android Espresso Kotlin Flow not collecting data

I had written an instrumentation test (Espresso), which opens the fragment and tests if the recycler view is visible or not.
In my view model, I had a call to the local database which returns the Flow<List> and only collectLatest{} and update the live data to display on the screen.
I had put some logs to display the ongoing data.
viewModelScope.launch {
Log.d("testing", "inside scope")
dao.getUsers().filter { it.isNotEmpty() }.collectLatest {
Log.d("testing", it.toString())
processData(it)
}
}
I had setup all the required idling resources for testing and as I am using the Hilt library for dependency injection, I had setup their launch fragment container too.
#Before
fun init() {
hiltRule.inject()
IdlingRegistry.getInstance().register(EspressoIdlingResource.countingIdlingResource)
IdlingRegistry.getInstance().register(idlingResourcesForDataBinding)
populateDatabase()
}
#After
fun tearDown() {
IdlingRegistry.getInstance().unregister(EspressoIdlingResource.countingIdlingResource)
IdlingRegistry.getInstance().unregister(idlingResourcesForDataBinding)
appDatabase.close()
}
#Test
fun test_list_isDisplayed() {
launchFragmentInHiltContainer<MyFragment> {
idlingResourcesForDataBinding.monitorFragment<MyFragment>(this)
}
onView(withId(R.id.recyclerview))
.check(matches(isDisplayed()))
Log output while testing
D/testing: inside scope
I had confirmed if the populateDatabase was successful or not by simply using the dao object in the test and calling the get data method which returns the required data successfully.
My observation so far
I had tried with collect{} and collectLatest{} both flow operators and first it emits [] empty list which means the db is empty but. once the insertion successful, any of the collect operator does not receiving the datat.

How to unit test suspend function with callback

I have the following code:
suspend fun initialize(sdk: Sdk) =
suspendCoroutine<Unit> { continuation ->
try {
sdk.initialize(
callback = { continuation.resume(Unit) },
onFailure = { error -> continuation.resumeWithException(SdkException(error.message)) })
} catch (exception: Exception) {
continuation.resumeWithException(
SdkException("Crash inside SDK", exception)
)
}
}
The Sdk is a third-party library. I'm using suspendCoroutine to suspend the coroutine and resume when the sdk finishes initializing.
Everything works fine but when I try to write a unit test like this I get the following IllegalStateException: This job has not completed yet. I'm using mockito-kotlin to write the following test:
#Test
fun `should initialize sdk correctly`() = runBlockingTest {
val sdk = mock<Sdk>()
initializeSdk(sdk)
verify(sdk).initialize(any(), any())
}
Basically what I want to do is to be able to test the resume and resumeWithException
Personally I'm not a fan of mocks. If Sdk is an interface, you could just provide your own test implementation to perform your tests (for success and error results). You can control exactly when/if the callback is called etc.
If Sdk is a class that you can't control, you could create an interface to abstract the Sdk class away, and make your initialize method use your own interface instead. However I have to admit this is not ideal.
If you stick with mocking, usually mocking libraries have a way for you to use invocation arguments to mock responses to method calls. With Mockito, it should be something like:
val sdk = mock<Sdk> {
on { initialize(any(), any()) } doAnswer { invocation ->
#Suppress("UNCHECKED_CAST")
val successCallback = invocation.arguments[0] as (() -> Unit) // use callback's function type here
successCallback.invoke()
}
}
(although I'm not an expert in Mockito, so there may be more concise or type-safe ways :D)

MockK - Failed matching mocking signature for left matchers: [any(), any()]

I want to implement some UI Tests to assure that the code implemented today works for tomorrow but when trying to see if already UI tests implemented in the past works, it throws this error:
Caused by: io.mockk.MockKException: Failed matching mocking signature for left matchers: [any(), any()]
This happens on an every {} return Unit line which there's a object file called WakeUpTimeManager, that calls a .set(param1, param2) function and inside that function there are some inline functions which I think it could be causing the problem but I don't know. I tried searching on the internet but couldn't find a solution.
Here's the test that throws the error:
#Before
fun setup() {
mockkObject(WakeUpTimerManager)
every { WakeUpTimerManager.set(any(), any()) } returns Unit
}
Here's the function that is calling on every line
fun set(context: Context, timer: Timer) {
if (timer.atMillis < System.currentTimeMillis()) {
return
}
if (Preset.findByID(context, timer.presetID) == null) {
return
}
//This is an inline function
withGson {
PreferenceManager.getDefaultSharedPreferences(context).edit {
putString(PREF_WAKE_UP_TIMER, it.toJson(timer))
}
}
//This is an inline function
withAlarmManager(context) {
it.setAlarmClock(
AlarmManager.AlarmClockInfo(timer.atMillis, getPendingIntentForActivity(context)),
getPendingIntentForService(context, timer)
)
}
}
Question: Why does mockk throw this error? What's going on? Is there any solution for this?
try with mockkStatic(WakeUpTimerManager::class). For me mockkObject was not working either, but mockkStatic did
In my case I was using the wrong annotation for mocking dependencies.
I was using #MockBean from org.springframework.boot.test.mock.mockito.MockBean while I should have been using #MockkBean from com.ninjasquad.springmockk.MockkBean.
In my case I used type cast for any(). I wanted to test that a method viewModel.show(Message()) had invoked. But this method is overloaded (has signatures of different types), so I tried to cast parameter any() to Message.
// show is overloaded method
fun show(resourceId: Int) {}
fun show(text: String) {}
fun show(message: Message) {}
// But it threw the exception.
verify { viewModel.show(any() as Message) }
// This won't work because Message() object will be different
verify { viewModel.show(Message()) }
Maybe mocking for message will help, but not in my case.
// val message = mockk<Message>()
// every { Message() } returns message
// verify { viewModel.show(message) }
I had to add mockkStatic, because I used an extension method. For instance, fun ViewExtension.show():
mockkStatic(ViewExtension::class.java.name + "Kt") // Like "com.example...ViewExtensionKt"
Then mock a behaviour:
every { viewModel.show(Message()) } just Runs
verify { viewModel.show(any() as Message) }
Sometimes, especially with Dagger Hilt and global test modules that replace object instances with Mockk mocks, it's not entirely clear whether one works with the mock or the real object. For me it was exactly this - I had a missing dependency, so my real instance was not replaced with the mocked instance, so mockk answered with this really weird error:
io.mockk.MockKException: Failed matching mocking signature for
left matchers: [any()]
at io.mockk.impl.recording.SignatureMatcherDetector.detect(SignatureMatcherDetector.kt:99)
at io.mockk.impl.recording.states.RecordingState.signMatchers(RecordingState.kt:39)
at io.mockk.impl.recording.states.RecordingState.round(RecordingState.kt:31)
at io.mockk.impl.recording.CommonCallRecorder.round(CommonCallRecorder.kt:50)
at io.mockk.impl.eval.RecordedBlockEvaluator.record(RecordedBlockEvaluator.kt:63)
at io.mockk.impl.eval.VerifyBlockEvaluator.verify(VerifyBlockEvaluator.kt:30)
at io.mockk.MockKDsl.internalVerify(API.kt:119)
at io.mockk.MockKKt.verify(MockK.kt:149)
at io.mockk.MockKKt.verify$default(MockK.kt:140)

Using Mockk to mock private function that takes a lambda

I am trying to write a unit test for a implementation of an abstract class I wrote. The method I'm trying to mock takes a lambda as it's only parameter. I'm trying to capture this lambda, so I can invoke it and get the result.
This is the method I'm trying to mock:
protected fun update(block: suspend S.() -> S?): Unit
I am using an extension function in my tests like this:
suspend inline fun <reified T : Model<S>, S : State> T.blah(
state: S,
block: (T) -> Unit
): S? {
val spy = spyk(this, recordPrivateCalls = true)
val slot = slot<suspend S.() -> S?>()
every { spy["update"](capture(slot)) } answers { Unit }
block(spy)
return slot.captured.invoke(state)
}
So I am creating a spy, then a slot, then when the update function is called, capture it so that it blocks the actual class from performing the call. Then I invoke the lambda myself and return the value.
However I keep getting this error:
io.mockk.MockKException: can't find function update(kotlin.jvm.functions.Function2$Subclass1#6bfa228c) for dynamic call
at io.mockk.InternalPlatformDsl.dynamicCall(InternalPlatformDsl.kt:122)
at io.mockk.MockKMatcherScope$DynamicCall.invoke(API.kt:1969)
I followed the stacktrace and set a breakpoint in the InternalPlatformDsl.kt class, and traced it to this block of code:
for ((idx, param) in it.parameters.withIndex()) {
val classifier = param.type.classifier
val matches = when (classifier) {
is KClass<*> -> classifier.isInstance(params[idx])
is KTypeParameter -> classifier.upperBounds.anyIsInstance(params[idx])
else -> false
}
if (!matches) {
return#firstOrNull false
}
}
It successfully matches the first parameter which is the class under test Model in this case, but it fails matching the second parameter because it is wrapped in the capture function.
Any ideas on how I can intercept this update call?
I'm using the latest version of mockk, and JUnit 4

Method is called multiple times in unit tests but not in code

I am writing unit tests to a class that uses RxJava 2. When onNext() is called on the observer I expect onMenuLoaded() to be called once. In code it is called successfully once. But when I test this piece in unit tests the method is invoked 3 times.
The questions are how to make it be called only once in tests and why it is called more times in tests than in the actual code.
//in ViewModel class, under testing
fun loadMenu() {
menuInteractorImpl.getMainMenu()?.subscribeOn(Schedulers.io())?.observeOn(AndroidSchedulers.mainThread())?.subscribe(
{ items ->
onMenuLoaded(items)
},
{ error ->
//error handling logic
}
)?.let { compositeDisposables.add(it) }
}
//Test
#RunWith(PowerMockRunner::class)
#PowerMockRunnerDelegate(MockitoJUnitRunner::class)
#PrepareForTest(MenuInteractorImpl::class, MainMenuViewModel::class)
class MainMenuViewModelTest {
#get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
companion object {
#ClassRule
#JvmField
val schedulers = RxImmediateSchedulerRule()
}
#Before
fun setUp() {
doNothing().`when`(viewModel).startTimerToScrollViewPager()
}
#Test
fun `test load menu calls onMenuLoaded when success`() {
val mockObservable = Observable.just(mockDataFactory.mockMenu).doOnNext {
viewModel.onMenuLoaded(it)
}.subscribeOn(Schedulers.newThread())
Mockito.`when`(menuInteractorImpl.getMainMenu()).thenReturn(mockObservable)
viewModel.loadMenu() //this method is called 3 times
Mockito.verify(viewModel, times(1)).onMenuLoaded(any())
}
From the logs it is shown where the method is called
viewModel.loadMenu();
-> at com.example.mainmenu.MainMenuViewModelTest.test load menu calls onMenuLoaded when success(MainMenuViewModelTest.kt:88)
viewModel.loadMenu();
-> at org.powermock.core.MockGateway.doMethodCall(MockGateway.java:182)
viewModel.loadMenu();
-> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Your real code is calling onMenuLoaded(items) in onNext and then your mock observable is additionally calling it from doOnNext.
You need to remove the doOnNext part of your mock observable:
#Test
fun `test load menu calls onMenuLoaded when success`() {
val mockObservable = Observable.just(mockDataFactory.mockMenu)
.subscribeOn(Schedulers.newThread())
Mockito.`when`(menuInteractorImpl.getMainMenu()).thenReturn(mockObservable)
viewModel.loadMenu() //this method is called 3 times
Mockito.verify(viewModel, times(1)).onMenuLoaded(any())
}
The problem was in accidentally combining PowerMockito spy and Mockito mock. When importing Mockito's spy the issue was resolved.
EDIT:
I faced the same issue again and the solution was a different one. It looks like the same cause is combining PowerMockito and Mockito. I solved it this time by adding a doNothing block
doNothing().`when`(viewModel).myCall(true)

Categories

Resources