我正试图测试我的代码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
。
一种方法:使用doAnswer
和any
返回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))
是创建即时期货的更好方法。