我正在为PlayFramework Scala应用程序创建单元测试,并且遇到了需要测试的功能,该功能进行了命令行界面调用。此cli调用无法在我们的测试环境中运行,因此我想对其进行模拟。
class Foo @Inject()(val bar: Bar, val a: A, val b: B...) {
def testThis(...) = {
...
callCommandLine
...
}
}
class Bar() {
def callCommandLine(s: String): String = {
...
}
}
下面是我尝试过的
class FooSpec() {
"testFoo" in {
val foo = app.injector.instanceOf[Foo]
val result = testThis(...)
val bar = mock[Bar]
val mockedOutput = "fake cmd line result"
when(bar.callCommandLine(anyString)).thenReturn(mockedOutput)
result mustBe mockedOutput
}
}
我了解为什么我的测试无法正常工作,但是我不知道我需要做些什么才能使其正常工作。我是否应该将模拟的bar类注入foo?
答案 0 :(得分:2)
假设您的代码实际上是
class Foo @Inject()(val bar: Bar, val a: A, val b: B...) {
def testThis(...) = {
...
bar.callCommandLine() // <-- difference here
...
}
}
由于您正在使用“实际”应用程序及其依赖项注入容器来构造Foo
,因此您面临的障碍正在出现。在“实际”应用中,Bar
显然绑定到实际的Bar
实例,而不是您在测试中创建的模拟实例。
要解决此问题,您有两种选择:
"testFoo" in {
val mockedBar = mock[Bar]
when(mockedBar.callCommandLine(anyString)).thenReturn(...)
val foo = new Foo(mockedBar, mock[A], mock[B], ...)
foo.testThis shouldBe "expectedResult"
}
这种方法简单明了,但是完全模拟了其他依赖项(A
,B
等)。在大多数情况下,这是可接受的(甚至是理想的)结果,因为它允许独立于依赖项行为测试Foo
中的代码。
缺点很明显-这不是集成测试,因此覆盖的范围较小(即A
和B
行为),并且不测试要在生产中使用的实际组件,因为它不以任何方式涉及依赖项注入。
我个人建议使用这种方式-它会创建一个更“独立”或“正交”的测试,并允许以其所有依赖项的不同行为测试Foo
。
Bar
的模拟。val mockedBar = mock[Bar]
val app = new GuiceApplicationBuilder()
.overrides(bind[Bar].toInstance(mockedBar))
.build()
"testFoo" in {
when(mockedBar.callCommandLine(anyString)).thenReturn(...)
val foo = app.injector.instanceOf[Foo]
foo.testThis shouldBe "expectedResult"
}
请注意,此代码段在使用后不会重置模拟,但这对于使用beforeEach
来说应该是微不足道的。更好的方法是为每个测试创建一个mockedBar
的新实例,但为了简洁起见,在此将其省略。
PlayFramework documentation中有一个部分介绍了此特定用例。
此方法更像集成测试-除Bar
以外的所有依赖项均使用实际实现,并具有其带来的所有优点和缺点。