Search code examples
androidunit-testingkotlinsharedpreferencesmockk

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


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:

enter image description here


Solution

  • 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) }
    }