23

嗨,我正在尝试使用我的演示者类调用的委托器来模拟从单一可观察对象中获得的响应,该可观察对象从改造中返回,并且我收到以下错误:

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) 并没有找到答案错误时,它失败了

这是我正在使用的 kotlin 扩展插件http://mockk.io/

4

5 回答 5

14

在您的情况下,您嘲笑了正在测试的课程。你有两个选择:

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

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

在此处阅读有关模拟、间谍和轻松模拟的更多信息:https ://blog.kotlin-academy.com/mocking-is-not-rocket-science-mockk-features-e5d55d735a98

于 2018-01-25T06:23:51.553 回答
10

首先,我建议你调试你的测试。然后你会发现你的哪一行代码运行失败。我和你有同样的经历,但在我的情况下,我的测试在到达时失败了onSuccess,例如从你的代码是:

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

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

@RelaxedMockK
lateinit var viewCallback: LoginContract.LoginViewCallBack

not answer found error就我而言,在我用轻松的模拟更改此界面后,错误解决了。

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

于 2019-01-18T05:43:05.180 回答
2

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

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

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

    // rest of your code
}

现在,您可以轻松地向您的登录演示者提供模拟或伪造。您也不会公开依赖项。如果您使用注入,您可以presenter.delegator从您的活动中调用,这可能是您不想要的。


边注:

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

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它不再是一个模拟。

于 2018-07-12T23:44:30.267 回答
2

如果您正在使用 mockk 并寻找模拟 void 方法,那么您需要做的就是添加:

returns Unit

或将其更改为以下形式:

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

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

于 2020-09-23T04:29:55.587 回答
1

您可以创建一个空测试以将问题减少到测试或上下文。

@Test
fun `test without body for check context errors`() = runBlocking { }

就我而言,有必要允许使用没有特定行为的创建@param relaxed


从:private val configuration = mockk<Configuration>()

至:private val configuration = mockk<Configuration>(relaxed = true)

于 2021-09-10T04:12:42.200 回答