如何为单个Scala Mockito测试用例启用宽松模式保留冗余桩?
问题场景
被测试代码
import concurrent.{ExecutionContext, Future} trait Backend { def verifyUser(): Future[Unit] def accounts(): Future[String] } class Service(backend: Backend) { private implicit val executor: ExecutionContext = ExecutionContext.global def accounts(): Future[String] = (for (_ <- backend.verifyUser(); accs <- backend.accounts()) yield accs) .recover { case ex => s"failed: ${ex.getMessage}" } }
测试代码
import org.scalatest._ import matchers.should import flatspec.AsyncFlatSpec import org.mockito.scalatest.AsyncIdiomaticMockito class ServiceTest extends AsyncFlatSpec with AsyncIdiomaticMockito with should.Matchers { private trait Fixture { lazy val backend: Backend = mock[Backend] lazy val service: Service = new Service(backend) } it should "return the reason of a failure" in { val fixture = new Fixture {} import fixture._ backend.verifyUser() returns Future.failed(new RuntimeException("verifyUser")) backend.accounts() returns Future.failed(new RuntimeException("accounts")) service.accounts() map { result => result should startWith ("failed: ") } } }
报错信息
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
- -> at ServiceTest.$anonfun$new$1(StrictStubbingTest.scala:25)
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.
org.mockito.exceptions.misusing.UnnecessaryStubbingException:
由于verifyUser()执行失败后,accounts()方法不会被调用,导致对应的桩被Mockito判定为冗余,抛出上述异常。要求保留两个桩的同时让测试通过,且仅为当前测试用例启用宽松模式。
解决方案
有两种精准控制的实现方式:
方式一:为单个冗余桩标记宽松模式
直接对不会被触发的backend.accounts()桩使用lenient()包装,仅让这个桩跳过未调用校验,其他桩仍保持Mockito的严格校验规则:
it should "return the reason of a failure" in { val fixture = new Fixture {} import fixture._ backend.verifyUser() returns Future.failed(new RuntimeException("verifyUser")) // 为该冗余桩启用宽松模式,忽略未调用检查 lenient(backend.accounts()) returns Future.failed(new RuntimeException("accounts")) service.accounts() map { result => result should startWith ("failed: ") } }
方式二:为当前测试块启用全局宽松模式
如果需要让当前测试用例中的所有桩都跳过未调用校验,可以用lenient代码块包裹所有stub定义:
it should "return the reason of a failure" in { val fixture = new Fixture {} import fixture._ // 该代码块内的所有桩都启用宽松模式 lenient { backend.verifyUser() returns Future.failed(new RuntimeException("verifyUser")) backend.accounts() returns Future.failed(new RuntimeException("accounts")) } service.accounts() map { result => result should startWith ("failed: ") } }
两种方式都能满足需求,方式一更精准(仅针对冗余桩),方式二更适合需要批量设置的场景。
内容的提问来源于stack exchange,提问作者danissimo
相关产品推荐
相关产品推荐

