[英]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(不是惰性)的初始化按以下順序進行:
但是,當val被覆蓋時,它不會被多次初始化。 盡管您的service
屬性定義了兩次,但在構造超類(FooService)時,此覆蓋的val將顯得為空。
您可以在這里找到更多信息
根據您的示例判斷代碼(不知道我能否做到; P),我有兩個建議,希望您不要介意:
首先,我認為FooService.service
將FooService.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.