【问题标题】:Scala specs2 mocking a trait method returns always Nullpointer exceptionScala specs2 模拟 trait 方法总是返回 Nullpointer 异常
【发布时间】:2015-01-25 19:54:15
【问题描述】:

我有一个要模拟的特征,并在测试期间在另一个服务中使用该模拟特征。问题是,当我尝试模拟 indexDocuments 函数的返回值时,我收到了 Nullpointerexception。

测试方法:

"createDemand must return None if writing to es fails" in new WithApplication {
  val demandDraft = DemandDraft(UserId("1"), "socken bekleidung wolle", Location(Longitude(52.468562), Latitude(13.534212)), Distance(30), Price(25.0), Price(77.0))
  val es = mock[ElasticsearchClient]
  val sphere = mock[SphereClient]
  val productTypes = mock[ProductTypes]

  sphere.execute(any[ProductCreateCommand]) returns Future.successful(product)
  productTypes.demand returns ProductTypeBuilder.of("demand", ProductTypeDrafts.demand).build()
  // this line throws the nullpointer exception
  es.indexDocument(any[IndexName], any[TypeName], any[JsValue]) returns Future.failed(new RuntimeException("test exception"))

  val demandService = new DemandService(es, sphere, productTypes)
  demandService.createDemand(demandDraft) must be (Option.empty[Demand]).await
}

性状:

sealed trait ElasticsearchClient {
  implicit def convertListenableActionFutureToScalaFuture[T](x: ListenableActionFuture[T]): Future[T] = {
    val p = Promise[T]()
    x.addListener(new ActionListener[T] {
      def onFailure(e: Throwable) = p.failure(e)
      def onResponse(response: T) = p.success(response)
    })
    p.future
  }

  lazy val client = createElasticsearchClient()
  def close(): Unit
  def createElasticsearchClient(): Client

  def indexDocument(esIndex: IndexName, esType: TypeName, doc: JsValue): Future[IndexResponse] =
    client.prepareIndex(esIndex.value, esType.value).setSource(doc.toString()).execute()
  def search(esIndex: IndexName, esType: TypeName, query: QueryBuilder): Future[SearchResponse] =
    client.prepareSearch(esIndex.value).setTypes(esType.value).setQuery(query).execute()
}

例外

[error]    NullPointerException:   (DemandServiceSpec.scala:89)
[error] services.DemandServiceSpec$$anonfun$1$$anonfun$apply$8$$anon$2$$anonfun$8.apply(DemandServiceSpec.scala:89)
[error] services.DemandServiceSpec$$anonfun$1$$anonfun$apply$8$$anon$2$$anonfun$8.apply(DemandServiceSpec.scala:89)
[error] services.DemandServiceSpec$$anonfun$1$$anonfun$apply$8$$anon$2.delayedEndpoint$services$DemandServiceSpec$$anonfun$1$$anonfun$apply$8$$anon$2$1(DemandServiceSpec.scala:89)
[error] services.DemandServiceSpec$$anonfun$1$$anonfun$apply$8$$anon$2$delayedInit$body.apply(DemandServiceSpec.scala:81)
[error] play.api.test.WithApplication$$anonfun$around$1.apply(Specs.scala:23)
[error] play.api.test.WithApplication$$anonfun$around$1.apply(Specs.scala:23)
[error] play.api.test.PlayRunners$class.running(Helpers.scala:49)
[error] play.api.test.Helpers$.running(Helpers.scala:403)
[error] play.api.test.WithApplication.around(Specs.scala:23)
[error] play.api.test.WithApplication.delayedInit(Specs.scala:20)
[error] services.DemandServiceSpec$$anonfun$1$$anonfun$apply$8$$anon$2.<init>(DemandServiceSpec.scala:81)
[error] services.DemandServiceSpec$$anonfun$1$$anonfun$apply$8.apply(DemandServiceSpec.scala:81)
[error] services.DemandServiceSpec$$anonfun$1$$anonfun$apply$8.apply(DemandServiceSpec.scala:81)

如果您需要更多信息,请告诉我。

【问题讨论】:

  • 您能否创建一个测试项目来重现该问题?您可以尝试向Future.failed 添加类型注释,以便清楚地知道Future[IndexResponse] 是预期的吗?您可以尝试使用 Mockito 直接而不是 specs2 语法吗?
  • 不幸的是,将 IndexResponse 添加为 Future 类型并没有帮助。您可以在这里找到另一个可能发生相同问题的小项目:stackoverflow.com/questions/28111015/… 这可能是一个更好的测试项目?我会尝试直接使用 Mockito

标签: scala mockito specs2


【解决方案1】:

我发现 indexDocuments 调用中的 any[] 匹配器是问题所在。当我用它的实际值替换它们时:

"createDemand must return None if writing to es fails and deleteDemand should be called once with correct parameters" in new WithApplication {
  val demandDraft = DemandDraft(UserId("1"), "socken bekleidung wolle", Location(Longitude(52.468562), Latitude(13.534212)), Distance(30), Price(25.0), Price(77.0))
  val es = mock[ElasticsearchClient]
  val sphere = mock[SphereClient]
  val productTypes = mock[ProductTypes]

  sphere.execute(any[ProductCreateCommand]) returns Future.successful(product)
  sphere.execute(any[ProductDeleteByIdCommand]) returns Future.successful(product)
  productTypes.demand returns ProductTypeBuilder.of("demand", ProductTypeDrafts.demand).build()
  es.indexDocument(IndexName("demands"), TypeName("demands"), Json.toJson(demand)) returns Future.failed(new RuntimeException("test exception"))

  val demandService = new DemandService(es, sphere, productTypes)
  demandService.createDemand(demandDraft) must be (Option.empty[Demand]).await
}

【讨论】:

    【解决方案2】:

    我已经发生了很多这样的事情,并通过创建一个类(而不是一个特征)来提供给mock 来解决它:

    trait SomeTraitYouWantToMock {
      …
    }
    class MockableSomeTraitYouWantToMock extends SomeTraitYouWantToMock
    val whatever = mock[MockableSomeTraitYouWantToMock]
    

    【讨论】:

    • 我也试过这个,但不幸的是它并没有解决 Nullpointer。我认为这与 trait 中的惰性 val 或隐式转换有关...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-17
    相关资源
    最近更新 更多