我最近一直在学习Scala,现在,我正在尝试与Mockito进行一些学习测试,但我一直面临着一些非常奇怪的事情。
FooService.scala
Common Language Runtime Exceptions
FooServiceTest.scala
class FooService extends Serializable {
val service: FooDaoTrait = FooDao
val fooResult = service.getAllTheFoosFromDatabase
def putTheFoosInASet(): Set[String] = {
fooResult.split(",").toSet
}
}
所以在这里,当我运行测试时,Scala IDE会在我的类变量@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
}
}
上抛出唯一的java.lang.NullPointerException
。
但不知何故,当我在fooResult
内移动FooResult
时:
putTheFoosInASet()
没有抛出异常,一切运作良好......
为什么以前的模式会让我异常?我不确定什么会导致这个错误,并且在不同的方法上调用 def putTheFoosInASet(): Set[String] = {
val fooResult = service.getAlltheFoosFromDatabase
fooResult.split(",").toSet
}
并不是真正干净的imo。
任何意见将不胜感激。感谢。
答案 0 :(得分:1)
这对mockito来说不是问题。要解决此问题,请使用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似乎为null。
您可以找到更多信息here
通过你的榜样判断你的代码(不知道我能否; P),我有两个建议,希望你不要介意:
首先,如果您将其作为类构造函数参数传递,我认为FooService.service
会更好,更优雅。特别是如果你打算模拟服务,这就是人们通常这样做的方式(不仅在scala中,而且在其他语言中):
class FooService(service: FooDaoTrait) {
// ...
其次,fooResult
在我看来更像是对数据库的查询,因此它更适合成为一个函数(因为它应该在每次查询时都改变)
class FooService extends Serializable {
val service: FooDaoTrait = new FooDao("aa")
def fooResult = service.getAllTheFoosFromDatabase
// ...
请注意,如果你采纳了这些建议(或两者兼而有之),你就不会遇到这个问题。