如何在函数中模拟函数调用?

时间:2019-08-08 21:16:16

标签: scala playframework mockito scalatest

我正在为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?

1 个答案:

答案 0 :(得分:2)

假设您的代码实际上是

class Foo @Inject()(val bar: Bar, val a: A, val b: B...) {
    def testThis(...) = {
        ...
        bar.callCommandLine()  // <-- difference here
        ...
    }
}

由于您正在使用“实际”应用程序及其依赖项注入容器来构造Foo,因此您面临的障碍正在出现。在“实际”应用中,Bar显然绑定到实际的Bar实例,而不是您在测试中创建的模拟实例。

要解决此问题,您有两种选择:

  1. 手动创建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"
}

这种方法简单明了,但是完全模拟了其他依赖项(AB等)。在大多数情况下,这是可接受的(甚至是理想的)结果,因为它允许独立于依赖项行为测试Foo中的代码。

缺点很明显-这不是集成测试,因此覆盖的范围较小(即AB行为),并且不测试要在生产中使用的实际组件,因为它不以任何方式涉及依赖项注入。

我个人建议使用这种方式-它会创建一个更“独立”或“正交”的测试,并允许以其所有依赖项的不同行为测试Foo

  1. 创建一个特定于测试的依赖项注入容器,并在其中进行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以外的所有依赖项均使用实际实现,并具有其带来的所有优点和缺点。