Scala mock when/thenReturn Future null



我正试图测试我的代码scalatestplus-play(4.0.3)。我也使用mockito-core(3.7.7)。我的测试代码不工作。它以异常结尾,如;

[info]   java.lang.NullPointerException: Cannot invoke "scala.concurrent.Future.flatMap(scala.Function1, scala.concurrent.ExecutionContext)" because "this.future" is null
[info]   at core.utils.Extensions$GetOrFail.getOrFailWith(Extensions.scala:12)
[info]   at services.ShowcaseService.update(ShowcaseService.scala:42)
[info]   at services.ShowcaseServiceSpec.$anonfun$new$5(ShowcaseServiceSpec.scala:59)
[info]   at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18)
[info]   at org.scalatest.OutcomeOf.outcomeOf(OutcomeOf.scala:85)
[info]   at org.scalatest.OutcomeOf.outcomeOf$(OutcomeOf.scala:83)
[info]   at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104)
[info]   at org.scalatest.Transformer.apply(Transformer.scala:22)
[info]   at org.scalatest.Transformer.apply(Transformer.scala:20)
[info]   at org.scalatest.WordSpecLike$$anon$3.apply(WordSpecLike.scala:1075)

也是我的测试代码;

val showcase = Showcase("id", "title", None, StrategyType.manuel, active = false, RecordTime())
val updatedShowcase = Showcase("id", "titleChange", Option("descChange"), StrategyType.manuel, active = false, RecordTime())
val showcaseUpdateRequest: ShowcaseUpdateRequest = ShowcaseUpdateRequest(Option("titleChange"), Option("descChange"))
val id = "id"
when(showcaseRepository.findOneById(id)).thenReturn(Future(Some(showcase)))
when(showcaseRepository.update(showcaseUpdateRequest, id)).thenReturn(Future(updatedShowcase))
val result = showcaseService.update(showcaseUpdateRequest, id)

展示服务;

def update(req: ShowcaseUpdateRequest, id: ShowcaseId): Future[Showcase] = for {
_ <- showcaseRepository.findOneById(id).getOrFailWith(ShowcaseNotFoundException(id))
updatedShowcase <- showcaseRepository.update(req, id)
} yield updatedShowcase

最后是发生错误的隐式类;

implicit class GetOrFail[T](future: Future[Option[T]]) {
def getOrFailWith[E <: Throwable](ex: E)(implicit ec: ExecutionContext): Future[T] = future flatMap {
case None => Future.failed(ex)
case Some(t) => Future.successful(t)
}
}

根据堆栈跟踪出现问题是因为被模拟的showcaseRepository.update返回null,这是因为每次当任何被模拟的参数与实际接收的参数不匹配时,mockito返回null

一种方法:使用doAnswerany返回Future.failed对于任何意外的参数失败测试:

// Leave for expected result
when(showcaseRepository.update(showcaseUpdateRequest, id)).thenReturn(Future(updatedShowcase))
// For all unexpected arguments
doAnswer(args => Future.failed(new Exception("Unexpected arguments for `update`: $args"))).when(showcaseRepository).update(any, any)

看起来你从showcaseRepository.findOneById(id)得到了null

最有可能的是,它的发生是因为使用不同的参数,您给mock的内容(看起来您在那里进行了一些隐式转换,因为您的id是一个String,但函数似乎想要ShowcaseId)。

所以,当你这样做的时候:when(showcaseRepository.findOneById(id)).thenReturn(Future(Some(showcase)))它的基本意思是,当findOneById被调用时使用这个参数,我希望它返回这个值

但是,根据隐式转换的实现方式,最终调用该函数的ShowcaseId的值可能最终与设置mock时的值不同。

也可能是这样的情况,你的代码做了一些错误的事情,调用函数的参数值是错误的,所以测试正确失败,只是以一种难以解释的方式。

由于这个原因,在存根调用时通常不使用实际的参数值是一个好主意,而是在之后使用verify:

when(showcaseRepository.findOneById(any)).thenReturn(Future(Some(showcase))
... // do the test
verify(showcaseRepository).findOneById(id)

此设置将使findOneById始终返回该值,而不管其参数是什么,然后,如果函数实际上使用不同的参数值调用,它将用有意义的消息(如"用此值而不是那个值调用")而不是一个神秘的NPE使verify失败。

作为旁注,Future.successful(Some(showcase))是创建即时期货的更好方法。

最新更新