'MockK "io.mockk.MockKException: no answer found for:" error
Hi i am trying to mock the response i get from a Single observable that gets returned from retrofit using a delegator that my presenter class calls and i am getting the following error:
io.mockk.MockKException: no answer found for: LoginPresenter(#1).login(LoginRequest([email protected], password=password123))
Here is my test code
@Test
fun testKotlinMock(){
val presenter : LoginPresenter = mockk<LoginPresenter>()
val delegator = mockk<AccountDelegatorContract>()
val viewCallback = mockk<LoginContract.LoginViewCallBack>()
val cookieStore = mockk<PianoCookieStore>()
val loginRequest = LoginRequest("[email protected]", "password123")
val customerResponse = CustomerResponse("jon", "richy")
every { delegator.login(loginRequest) } returns Single.just(Response.success(any()))
every { delegator.getCustomer() } returns Single.just(customerResponse)
every { presenter.loginViewCallBack } returns viewCallback
every { presenter.accountDelegator } returns delegator
every { presenter.cookieStorage } returns cookieStore
presenter.login(loginRequest)
}
My actual Presenter code looks like this:
@Inject
lateinit var loginViewCallBack: LoginViewCallBack
@Inject
lateinit var delegator: DelegatorContract
@Inject
lateinit var cookieStorage: CookieStore
@Inject
constructor()
override fun login(loginRequest: LoginRequest) {
delegator.login(loginRequest)
.flatMap({ response ->
saveCookieAndContinue(response)
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(object : SingleObserver<CustomerResponse>{
override fun onSubscribe(d: Disposable) {
}
override fun onError(e: Throwable) {
loginViewCallBack.onErrorLogin(PianoError.ERROR_LOGIN_INVALID)
Log.d("JJJ", "login error")
}
override fun onSuccess(customerResponse : CustomerResponse) {
loginViewCallBack.onLoginSuccess(customerResponse)
Log.d("JJJ", "login successfully")
}
})
}
private fun saveCookieAndContinue(response: Response<Void>): Single<CustomerResponse> {
if (response.isSuccessful) {
val headers = response.headers()
cookieStorage.saveSessionCookies(headers.get(PianoCookieStore.COOKIE_HEADER_SET_NAME)!!)
return accountDelegator.getCustomer()
}
//TODO: Change this to throw a login exception?
throw RuntimeException()
}
i basically want to mock the injected dependencies you see from the main code and then run a happy path unit test.
It fails when i call the presenter.login(loginRequest) with the no answer found error
This is the kotlin extenstion plugin i am using http://mockk.io/
Solution 1:[1]
In your case you mocked the classes being tested. You have two options:
- get rid of mockk for loginPresenter, just use original object and set properties
- use
spyk
to create spy. This is something in between original object and mock
The exception is throw because mocks are strict by default, it just do not know how to handle it because mocks as objects are not initialized at all.
Read more about mocks, spies and relaxed mocks here: https://blog.kotlin-academy.com/mocking-is-not-rocket-science-mockk-features-e5d55d735a98
Solution 2:[2]
First of all, I suggest you to debug your test. Then you will find which line of your code failed run. I get the same experienced with you, but in my case, my test failed when it reached on onSuccess
, for example from your code is:
override fun onSuccess(customerResponse : CustomerResponse) {
loginViewCallBack.onLoginSuccess(customerResponse)
Log.d("JJJ", "login successfully")
}
I think from your test will fail after it hits line loginViewCallBack.onLoginSuccess(customerResponse)
, because loginViewCallback
is not found in your mock test. If you have interface class that you want to mock, you should write it be:
@RelaxedMockK
lateinit var viewCallback: LoginContract.LoginViewCallBack
in my case, error not answer found error
resolved after I changed this interface with relaxed mock.
from the docs: Relaxed mock is the mock that returns some simple value for all functions. This allows to skip specifying behavior for each case, while still allow to stub things you need. For reference types chained mocks are returned.
Solution 3:[3]
You should not mock the class under test. Using a spy is okay, though, if you need to verify a method was called, for example.
I would recommend to not using injection in classes you control. DI frameworks like Dagger are great for classes you don't create, such as Activities and Fragments, but for classes you do control, just use the constructor.
class LoginPresenter(private val loginViewCallBack: LoginViewCallBack,
private val delegator: DelegatorContract,
private val cookieStorage: CookieStore) {
// rest of your code
}
Now you can easily provide a mock, or fake, to your login presenter. You also don't expose the dependencies. You could call presenter.delegator
from your activity if you use injection, which you probably don't want.
Side note:
with your LoginPresenter using the constructor, and using dagger, you would create the presenter like:
class LoginModule {
@Provides
@ActivityScope
internal providePresenter(loginViewCallBack: LoginViewCallBack,
delegator: DelegatorContract,
cookieStorage: CookieStore): LoginPresenter = LoginPresenter(loginViewCallBack, delegator, cookieStorage)
}
If you want to use injection instead, you just need to remember to set the mocks:
@Test
fun `test authentication fails`() {
val loginViewCallBack = mockk<LoginViewCallBack>()
val delegator = mockk<DelegatorContract>()
val cookieStorage = mockk<CookieStore>()
val presenter = LoginPresenter()
presenter.loginViewCallBack = loginViewCallBack
presenter.delegator = delegator
presenter.cookieStorage = cookieStorage
val loginRequest: LoginRequest = ... //mock, fake, or real object
every { delegator.login(loginRequest) } returns Single.error(RuntimeException("oops!"))
presenter.login(loginRequest)
verify { loginViewCallBack.onErrorLogin(PianoError.ERROR_LOGIN_INVALID) }
}
The example above will get rid of the "no answer found" for presenter.login(request)
since presenter
is no longer a mockk.
Solution 4:[4]
If you are using mockk and looking for mocking void method then all you need to do is adding :
returns Unit
or change it to this forms :
doNothing().`when`(mockedFile).write(any())
as described in https://notwoods.github.io/mockk-guidebook/docs/mockito-migrate/void/
Solution 5:[5]
In my case I forgot to check that one method was called (for instance, in code we have object.setData(person.getAge())
).
every { object.setData(any()) } just Runs // Mock method
testedMethod() // Tested code runs here
verify { object.setData(any()) } // Check that object.setData() was called
Solution 6:[6]
You could creating an empty test to reduce the problem to the test or context.
@Test
fun `test without body for check context errors`() = runBlocking { }
In my case, it was necessary to allow creation with no specific behaviour using @param relaxed
From: private val configuration = mockk<Configuration>()
To: private val configuration = mockk<Configuration>(relaxed = true)
Solution 7:[7]
In my case I have a function that access a setter but with Kotlin syntax and mockk
was throwing an exception because you have to define the setter behavior. This can be done in two ways, depending on whether you want to read the value later or not.
If you want to read the value, you can setup a slot
:
val myPropertySlot = slot<Int>()
every { myClass.myProperty = capture(myPropertySlot) } just runs
assertThat(myPropertySlot.captured).isEqualTo(1)
If you just want the setter to run but you won't need the value, the syntax gets easier:
every { myClass.myProperty = any() } just runs
This will make your test not throw if you try to access myProperty
like this in the code being tested:
...
myClass.myProperty = 1
...
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | oleksiyp |
Solution 2 | audrians |
Solution 3 | Jamie Dulaney |
Solution 4 | Joe |
Solution 5 | CoolMind |
Solution 6 | Braian Coronel |
Solution 7 | anon37894203 |