The application in question has a logout function that among other things, performs a call to CookieManager.getInstance().removeAllCookies(null):
fun logout(...) {
showLoader(true)
msgRepo.unregisterSendBirdHandlersAndPushToken(
onSuccess = {
clearUserData(...)
// ...
onLogout()
},
onFailure = {
clearUserData(...)
// ...
onLogout()
}
)
}
/**
* Helper to clear user data before logging user out
*/
private fun clearUserData(...) {
// ...
CookieManager.getInstance().removeAllCookies(null)
}
Two tests reference the aforementioned logout function. They both are quite similar and look a bit like this:
#Test
fun `verify logout with success`() {
// .. Other code
HardLogoutHelper.logout(...)
// ...
}
The tests fail with the following error message:
'void android.webkit.CookieManager.removeAllCookies(android.webkit.ValueCallback)'
java.lang.NoSuchMethodError: 'void android.webkit.CookieManager.removeAllCookies(android.webkit.ValueCallback)'
at com.example.helpers.HardLogoutHelper.clearUserData(HardLogoutHelper.kt:86)
I've tried running the test with Roboelectric via #RunWith(RoboelectricTestRunner::class) and I have have tried creating a custom Roboelectric Shadow for the CookieManager class. The issue seems to persist even when using Roboelectric.
Why is the previously mentioned error message being thrown, and how can it be fixed?
Related
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) }
}
Ive been struggling with this for quite some time now, perhaps someone could help...
I have this function in my class under test:
fun launchForegroundTimer(context: Context) {
helper.log("AppRate", "[$TAG] Launching foreground count down [10 seconds]")
timerJob = helper.launchActionInMillisWithBundle(Dispatchers.Main, TimeUnit.SECOND.toMillis(10), context, this::showGoodPopupIfAllowed)
}
So in that function, I first write to some log and then I call a coroutine function that expects a Dispatcher param, how long to wait before running the action, Any object that I would like to pass on to the action and the actual action function that is invoked when time has passed.
So in this case, the this::showGoodPopupIfAllowed which is a private method in the class, gets called when the 10,000 ms have passed.
Here is that function:
private fun showGoodPopupIfAllowed(context: Context?) {
if (isAllowedToShowAppRate()) {
showGoodPopup(context)
}
}
In that first if, there are a bunch of checks that occur before I can call showGoodPopup(context)
Now, here is the helper.launchActionInMillisWithBundle function:
fun <T> launchActionInMillisWithBundle(dispatcher: CoroutineContext, inMillis: Long, bundle: T, action: (T) -> Unit): Job = CoroutineScope(dispatcher).launchInMillisWithBundle(inMillis, bundle, action)
And here is the actual CoroutineScope extension function:
fun <T> CoroutineScope.launchInMillisWithBundle(inMillisFromNow: Long, bundle: T, action: (T) -> Unit) = this.launch {
delay(inMillisFromNow)
action(bundle)
}
What I am trying to achieve is a UnitTest that calls the launchForegroundTimer function, calls the helper function with the appropriate arguments and also continue through and call that lambda showGoodPopupIfAllowed function where I can also provide mocked behaviour to all the IF statments that occur in isAllowedToShowAppRate.
Currently my test stops right after the launchActionInMillisWithBundle is called and the test just ends. I assume there is no real call to any coroutine because I am mocking the helper class... not sure how to continue here.
I read a few interesting articles but none seems to resolve such state.
My current test function looks like this:
private val appRaterManagerHelperMock = mockkClass(AppRaterManagerHelper::class)
private val timerJobMock = mockkClass(Job::class)
private val contextMock = mockkClass(Context::class)
#Test
fun `launch foreground timer`() {
every { appRaterManagerHelperMock.launchActionInMillisWithBundle(Dispatchers.Main, TimeUnit.SECOND.toMillis(10), contextMock, any()) } returns timerJobMock
val appRaterManager = AppRaterManager(appRaterManagerHelperMock)
appRaterManager.launchForegroundTimer(contextMock)
verify(exactly = 1) { appRaterManagerHelperMock.log("AppRate", "[AppRaterManager] Launching foreground count down [10 seconds]") }
}
I'm using mockk as my Mocking lib.
AppRaterManager is the class under test
I'd like to also mention that, in theory I could have moved the coroutine invocation outside the class under test. So an external class like activity.onResume() could launch some sort of countdown and then call directly a function that checks showGoodPopupIfAllowed(). But currently, please assume that I do not have any way to change the calling code so the timer and coroutine should remain in the class under test domain.
Thank you!
Alright, I read a bit deeper into capturing/answers over at https://mockk.io/#capturing and saw there is a capture function.
So I captured the lambda function in a slot which enables me invoke the lambda and then the actual code continues in the class under test. I can mock the rest of the behavior from there.
Here is my test function for this case (for anyone who gets stuck):
#Test
fun `launch foreground timer, not participating, not showing good popup`() {
val slot = slot<(Context) -> Unit>()
every { appRaterManagerHelperMock.launchActionInMillisWithBundle(Dispatchers.Main, TimeUnit.SECOND.toMillis(10), contextMock, capture(slot)) } answers {
slot.captured.invoke(contextMock)
timerJobMock
}
every { appRaterManagerHelperMock.isParticipating() } returns false
val appRaterManager = AppRaterManager(appRaterManagerHelperMock)
appRaterManager.launchForegroundTimer(contextMock)
verify(exactly = 1) { appRaterManagerHelperMock.log("AppRate", "[AppRaterManager] Launching foreground count down [10 seconds]") }
verify(exactly = 1) { appRaterManagerHelperMock.isParticipating() }
verify(exactly = 0) { appRaterManagerHelperMock.showGoodPopup(contextMock, appRaterManager) }
}
So what's left now is how to test the coroutine actually invokes the lambda after the provided delay time is up.
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)
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)
I am testing Kotlin coroutines in my Android app and I am trying to do the following unit test
#Test fun `When getVenues success calls explore venues net controller and forwards result to listener`() =
runBlocking {
val near = "Barcelona"
val result = buildMockVenues()
val producerJob = produce<List<VenueModel>>(coroutineContext) { result.value }
whenever(venuesRepository.getVenues(eq(near))) doReturn producerJob // produce corooutine called inside interactor.getVenues(..)
interactor.getVenues(near, success, error) // call to real method
verify(venuesRepository).getVenues(eq(near))
verify(success).invoke(argThat {
value == result.value
})
}
The interactor method is as follows
fun getVenues(near: String, success: Callback<GetVenuesResult>,
error: Callback<GetVenuesResult>) =
postExecute {
repository.getVenues(near).consumeEach { venues ->
if (venues.isEmpty()) {
error(GetVenuesResult(venues, Throwable("No venues where found")))
} else {
success(GetVenuesResult(venues))
}
}
}
postExecute{..} is a method on a BaseInteractor that executes the function in the ui thread through a custom Executor that uses the launch(UI) coroutine from kotlin android coroutines library
fun <T> postExecute(uiFun: suspend () -> T) =
executor.ui(uiFun)
Then the repository.getVenues(..) function is also a coroutine that returns the ProducerJob using produce(CommonPool) {}
The problem is that it seams that success callback in the interactor function doesn't seem to be executed as per the
verify(success).invoke(argThat {
value == result.value
})
However, I do see while debugging that the execution in the interactor function reaches to the if (venues.isEmpty()) line inside the consumeEach but then from there exits and continues with the test, obviously failing on the verify for the success callback.
I am a bit new on coroutines so any help would be appreciated.
I figured this one out. I saw that the problem was just with this producing coroutine and not with the others tests that are also using coroutines and working just fine. I noticed that I actually missed the send on the mocked ProducingJob in order to have it actually produce a value, in this case the list of mocks. I just added that changing the mock of the producing job to
val producerJob = produce { send(result.value) }