在.NET环境中,有许多模拟(mocking)工具可以选择,例如Moq、FakeItEasy和RhinoMocks等。个人更熟悉Moq,因此在寻找JVM平台的模拟框架时,希望找到一个语法与.NET中相似的工具。在JVM平台上,有几个不错的选择,如ScalaMock、EasyMock、JMock和Mockito,它们都可以很好地与ScalaTest配合使用。在这些选项中,最终选择了Mockito,因为它的语法最符合喜好,并且它的文档齐全,Google搜索结果也很正面。
与之前的大多数文章一样,需要使用SBT来获取库。因此,SBT文件需要包含以下内容:
libraryDependencies ++= Seq(
"org.mockito" % "mockito-core" % "1.8.5",
"org.scalatest" %% "scalatest" % "2.2.5" % "test"
)
让来看一个简单的例子。这个例子模拟了一个java.util.ArrayList[String],并设置了一些验证。
class FlatSpec_Mocking_Tests extends FlatSpec with Matchers with MockitoSugar {
"Testing using Mockito" should "be easy" in {
// mock creation
val mockedList = mock[java.util.ArrayList[String]]
// using mock object
mockedList.add("one")
mockedList.clear()
// verification
verify(mockedList).add("one")
verify(mockedList).clear()
}
}
可能会注意到,如何能够模拟ArrayList[T]的,尽管它不是一个抽象类。这是非常酷的。
使用Mockito,也可以像预期的那样桩出东西。这里有一个例子,尝试模拟一个简单的特质(trait)。
trait DumbFormatter {
def formatWithDataTimePrefix(inputString: String, date: Date): String = {
s"date : $date : $inputString"
}
def getDate(): String = {
new Date().toString
}
}
class FlatSpec_Mocking_Tests extends FlatSpec with Matchers with MockitoSugar {
"Stubbing using Mockito" should "be easy" in {
var mockDumbFormatter = mock[DumbFormatter]
when(mockDumbFormatter.getDate()).thenReturn("01/01/2015")
assert("01/01/2015" === mockDumbFormatter.getDate())
}
}
在上面的例子中,可以看到模拟一个特质是多么容易。也可以看到如何使用Mockito函数when和thenReturn来桩出模拟的。
刚刚看到了如何使用Mockito函数thenReturn来设置返回值。如果想设置一个动态返回值,这可以很容易地调用一些其他函数来创建返回值。
Mockito提供了一些功能,允许匹配任何参数值。它还提供了正则表达式匹配器,并允许编写自定义匹配器,如果内置的匹配器不满足需求。
class FlatSpec_Mocking_Tests extends FlatSpec with Matchers with MockitoSugar {
"Stubbing using Mockito" should "be easy" in {
var mockDumbFormatter = mock[DumbFormatter]
when(mockDumbFormatter.formatWithDataTimePrefix(anyString(), any[Date]())).thenReturn("01/01/2015 Something")
assert("01/01/2015 Something" === mockDumbFormatter.formatWithDataTimePrefix("blah blah blah", new Date()))
}
}
在上面的例子中,使用了标准的参数匹配器。
要使用Mockito抛出异常,只需要使用thenThrow(…)函数。
class FlatSpec_Mocking_Tests extends FlatSpec with Matchers with MockitoSugar {
"Stubbing using Mockito" should "be easy" in {
var mockDumbFormatter = mock[DumbFormatter]
when(mockDumbFormatter.formatWithDataTimePrefix(anyString(), any[Date]())).thenThrow(new RuntimeException())
intercept[RuntimeException] {
mockDumbFormatter.formatWithDataTimePrefix("blah blah blah", new Date())
}
}
}
如所见,还需要使用ScalaTest的intercept来实际测试。
回调在想看到方法被调用了什么,然后可以做出明智的决定可能返回什么时很有用。
class FlatSpec_Mocking_Tests extends FlatSpec with Matchers with MockitoSugar {
"Stubbing using Mockito" should "be easy" in {
var mockDumbFormatter = mock[DumbFormatter]
when(mockDumbFormatter.formatWithDataTimePrefix(anyString(), any[Date]())).thenAnswer(new Answer[String] {
override def answer(invocation: InvocationOnMock): String = {
val result = "called back nicely sir"
println(result)
result
}
})
assert("called back nicely sir" === mockDumbFormatter.formatWithDataTimePrefix("blah blah blah", new Date()))
}
}
在上面的例子中,使用了Mockito的thenAnswer函数,以及如何使用匿名Answer对象。
最后,想谈谈验证。这可能包括验证函数是否被调用,以及是否被调用了正确次数。
class FlatSpec_Mocking_Tests extends FlatSpec with Matchers with MockitoSugar {
"Stubbing using Mockito" should "be easy" in {
var mockDumbFormatter = mock[DumbFormatter]
when(mockDumbFormatter.formatWithDataTimePrefix(anyString(), any[Date]())).thenReturn("someString")
val theDate = new Date()
val theResult = mockDumbFormatter.formatWithDataTimePrefix("blah blah blah", theDate)
val theResult2 = mockDumbFormatter.formatWithDataTimePrefix("no no no", theDate)
verify(mockDumbFormatter, atLeastOnce()).formatWithDataTimePrefix("blah blah blah", theDate)
verify(mockDumbFormatter, times(1)).formatWithDataTimePrefix("no no no", theDate)
}
}
在上面的例子中,验证了函数是否被调用,以及是否被调用了正确次数。