kotlin mockk没有回答发现错误

时间:2018-01-12 09:47:39

标签: unit-testing kotlin kotlin-android-extensions mockk

您好我正在尝试模拟我从一个使用我的演示者类调用的委托人从改造返回的单个observable得到的响应,我收到以下错误:

io.mockk.MockKException:找不到答案:LoginPresenter(#1).login(LoginRequest(email=hello@gmail.com,password = password123))

这是我的测试代码

 @Test
    fun testKotlinMock(){

        val presenter : LoginPresenter = mockk<LoginPresenter>()

        val delegator = mockk<AccountDelegatorContract>()

        val viewCallback = mockk<LoginContract.LoginViewCallBack>()

        val cookieStore = mockk<PianoCookieStore>()

        val loginRequest = LoginRequest("hello@gmail.com", "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)
    }

我的实际Presenter代码如下所示:

 @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()
    }

我基本上想要模拟从主代码中看到的注入依赖项,然后运行一个快乐的路径单元测试。

当我调用presenter.login(loginRequest)时找不到回答错误

这是我正在使用的http://mockk.io/

的kotlin扩展插件

5 个答案:

答案 0 :(得分:4)

在你的情况下,你嘲笑了被测试的类。您有两种选择:

  • 摆脱loginPresenter的mockk,只使用原始对象并设置属性
  • 使用spyk创建间谍。这是原始对象和模拟
  • 之间的事情

抛出异常是因为mocks默认是严格的,它只是不知道如何处理它,因为mocks作为对象根本没有初始化。

在这里阅读更多关于嘲笑,间谍和轻松嘲笑的内容:https://blog.kotlin-academy.com/mocking-is-not-rocket-science-mockk-features-e5d55d735a98

答案 1 :(得分:1)

您不应嘲笑被测类。但是,如果需要验证某个方法是否被调用,可以使用间谍。

我建议在您控制的类中使用注入。像Dagger这样的DI框架非常适合您不创建的类,例如Activity和Fragments,但是对于您要控制的类,只需使用构造函数即可。

class LoginPresenter(private val loginViewCallBack: LoginViewCallBack,
                     private val delegator: DelegatorContract,
                     private val cookieStorage: CookieStore) {

    // rest of your code
}

现在,您可以轻松地向登录演示者提供模拟或伪造。您也不会公开依赖项。如果您不希望使用注射,则可以在活动中致电presenter.delegator


旁注:

使用您的LoginPresenter使用构造函数,并使用匕首,您将创建演示者,如下所示:

class LoginModule {
    @Provides
    @ActivityScope
    internal providePresenter(loginViewCallBack: LoginViewCallBack,
                              delegator: DelegatorContract,
                              cookieStorage: CookieStore): LoginPresenter = LoginPresenter(loginViewCallBack, delegator, cookieStorage)
}

如果要改用注入,只需记住要设置模拟:

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

由于presenter.login(request)不再是模拟对象,因此上面的示例将消除presenter的“找不到答案”。

答案 2 :(得分:0)

首先,我建议您调试测试。然后,您将发现代码的哪一行运行失败。我的经历与您相同,但就我而言,当测试到达onSuccess时,我的测试失败了,例如,您的代码中的测试是:

override fun onSuccess(customerResponse : CustomerResponse) {
         loginViewCallBack.onLoginSuccess(customerResponse)
         Log.d("JJJ", "login successfully")
}

我认为您的测试在到达第loginViewCallBack.onLoginSuccess(customerResponse)行后会失败,因为在模拟测试中找不到loginViewCallback。如果您有要模拟的接口类,则应将其编写为:

@RelaxedMockK
lateinit var viewCallback: LoginContract.LoginViewCallBack

就我而言,使用轻松的模拟功能更改此接口后,错误not answer found error得到解决。

来自docs轻松的模拟是为所有函数返回一些简单值的模拟。这允许跳过每种情况的指定行为,同时仍然允许存根您需要的东西。对于引用类型,将返回链式模拟。

答案 3 :(得分:0)

有时错误是因为您错过了一个愚蠢的事情。 就我而言,我一直被遗漏在请求和响应类中使用“数据类”。 就这些。与“ relaxedMockks”或“间谍”无关。但是消息与您收到的消息相同:“未找到答案”。

请查看您的LoginRequest和CustomerResponse是数据类。

答案 4 :(得分:0)

如果您正在使用ockock并正在寻找嘲笑void方法,那么您要做的就是添加:

returns unit

或将其更改为以下形式:

doNothing().`when`(mockedFile).write(any())

https://notwoods.github.io/mockk-guidebook/docs/mockito-migrate/void/

中所述