簡體   English   中英

為什么我們不能在類變量上調用模擬方法?

[英]Why can't we call a mocked method on a class variable?

我最近一直在學習Scala,現在,我正在嘗試與Mockito進行一些學習測試,但是我一直面臨着很多奇怪的事情。

FooService.scala

class FooService extends Serializable {

  val service: FooDaoTrait = FooDao
  val fooResult = service.getAllTheFoosFromDatabase     

  def putTheFoosInASet(): Set[String] = {
    fooResult.split(",").toSet
  }
}

FooServiceTest.scala

@RunWith(classOf[JUnitRunner])
class FooServiceTest extends FunSuite with MockitoSugar {

  val m = mock[FooDaoTrait]
  when(m.getAllTheFoosFromDatabase).thenReturn("Hi, my name is foo")

  val instance = new FooService{ override val service = m }

  test("Let's get da Foos") {
    assert(instance.putTheFoosInASet().size === 3) // 3 is a random Value
  }
}

因此,在這里,當我運行測試時,Scala IDE會在類變量fooResult上拋出唯一的java.lang.NullPointerException

但是不知何故,當我在putTheFoosInASet()移動FooResult時:

  def putTheFoosInASet(): Set[String] = {
    val fooResult = service.getAlltheFoosFromDatabase  
    fooResult.split(",").toSet
  }

沒有異常被拋出,並且一切正常……為什么以前的模式會拋出異常? 我不太確定是什么會導致此錯誤,並且在不同方法上調用fooResult並不是真的干凈的imo。

任何意見將不勝感激。 謝謝。

簡短答案:

這不是嘲笑的問題。 要解決此問題,請使用lazy

class FooService extends Serializable {
  val service: FooDaoTrait = FooDao
  lazy val fooResult = service.getAllTheFoosFromDatabase    

// ... 

長答案:

讓我們忘記Mockito和Mocks。 我們仍然可以重現該問題:

val testFooDao = new FooDaoTrait {
  def getAllTheFoosFromDatabase() = "Hi, my name is foo"
}
val instance = new FooService { 
  override val service = testFooDao 
}
// ----> java.lang.NullPointerException

好的,這是怎么回事? 添加一些打印可以幫助我們理解:

class FooService extends Serializable {
  val service: FooDaoTrait = new FooDao("aa")
  println(s"Original FooService service is $service")
  val fooResult = service.getAllTheFoosFromDatabase
}

val testFooDao = new FooDaoTrait {
  def getAllTheFoosFromDatabase() = "Hi, my name is foo"
}
val instance = new FooService { 
  override val service = testFooDao 
  println(s"New FooService service is $service")
}

此打印

// Original FooService service is null 
// NullPointerException ...

這就是為什么我們得到NullPointerException的原因。 由於service為空,因此service.getAllTheFoosFromDatabase給出了此異常。

為什么會這樣呢?

在scala中,嚴格val(不是惰性)的初始化按以下順序進行:

  1. 超類在子類之前被完全初始化。
  2. 否則,按聲明順序。

但是,當val被覆蓋時,它不會被多次初始化。 盡管您的service屬性定義了兩次,但在構造超類(FooService)時,此覆蓋的val將顯得為空。

您可以在這里找到更多信息

額外

根據您的示例判斷代碼(不知道我能否做到; P),我有兩個建議,希望您不要介意:

首先,我認為FooService.serviceFooService.service作為類構造函數參數傳遞,它將更好,更優雅。 特別是,如果您打算模擬服務,這是人們通常這樣做的方式(不僅在scala中,而且在其他語言中):

class FooService(service: FooDaoTrait) {
// ...

其次,在我看來, fooResult更像是對數據庫的查詢,因此將它作為一個函數更有意義(因為它每次查詢時都會改變)

class FooService extends Serializable {
  val service: FooDaoTrait = new FooDao("aa")
  def fooResult = service.getAllTheFoosFromDatabase
// ...

請注意,如果您采納了這些建議(或兩者兼而有之),則不會遇到此問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM