如何模拟函数内的函数调用?
How to a mock a function call within a function?
我正在为 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 class 注入 foo 吗?
假设你的代码实际上是
class Foo @Inject()(val bar: Bar, val a: A, val b: B...) {
def testThis(...) = {
...
bar.callCommandLine() // <-- difference here
...
}
}
您面临的障碍是因为您正在使用 "actual" 应用程序及其依赖注入容器来构建 Foo
。在 "actual" 应用程序中,Bar
显然绑定到一个实际的 Bar
实例,而不是您在测试中装箱的模拟实例。
要解决此问题,您有两种选择:
- 手动创建 Foo 实例:
"testFoo" in {
val mockedBar = mock[Bar]
when(mockedBar.callCommandLine(anyString)).thenReturn(...)
val foo = new Foo(mockedBar, mock[A], mock[B], ...)
foo.testThis shouldBe "expectedResult"
}
这种方式简单明了,但是完全mock掉了其他依赖(A
、B
等)。在大多数情况下,这是一个可接受的(甚至是可取的)结果,因为它允许独立于依赖项行为测试 Foo
中的代码。
缺点很明显 - 这不是集成测试,所以它涵盖的范围较少(即 A
和 B
行为),并且不测试生产中要使用的实际组件,因为它不以任何方式涉及依赖注入。
就我个人而言,我建议采用这种方式 - 它将创建更多 "independent" 或 "orthogonal" 测试,并允许在它具有的所有依赖项的不同行为下测试 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
之外的所有依赖项都使用实际实现,具有它带来的所有优点和缺点。
我正在为 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 class 注入 foo 吗?
假设你的代码实际上是
class Foo @Inject()(val bar: Bar, val a: A, val b: B...) {
def testThis(...) = {
...
bar.callCommandLine() // <-- difference here
...
}
}
您面临的障碍是因为您正在使用 "actual" 应用程序及其依赖注入容器来构建 Foo
。在 "actual" 应用程序中,Bar
显然绑定到一个实际的 Bar
实例,而不是您在测试中装箱的模拟实例。
要解决此问题,您有两种选择:
- 手动创建 Foo 实例:
"testFoo" in {
val mockedBar = mock[Bar]
when(mockedBar.callCommandLine(anyString)).thenReturn(...)
val foo = new Foo(mockedBar, mock[A], mock[B], ...)
foo.testThis shouldBe "expectedResult"
}
这种方式简单明了,但是完全mock掉了其他依赖(A
、B
等)。在大多数情况下,这是一个可接受的(甚至是可取的)结果,因为它允许独立于依赖项行为测试 Foo
中的代码。
缺点很明显 - 这不是集成测试,所以它涵盖的范围较少(即 A
和 B
行为),并且不测试生产中要使用的实际组件,因为它不以任何方式涉及依赖注入。
就我个人而言,我建议采用这种方式 - 它将创建更多 "independent" 或 "orthogonal" 测试,并允许在它具有的所有依赖项的不同行为下测试 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
之外的所有依赖项都使用实际实现,具有它带来的所有优点和缺点。