使用在 Spock 中的 where 块中分配的参数断言模拟调用

Assert mock call with argument assigned in where block in Spock

我有一个方法,其中 生成一个标识符,将它发送到一些外部依赖项,然后 returns 它。我想要一个单元测试来测试 是否返回了发送到那里的相同值

假设测试和代码如下所示:

  def "test"() {
    given:
      def testMock = Mock(TestMock)
    and:
      def x
    when:
      def testClass = new TestClass()
      x = testClass.callMethod(testMock)
    then:
      1 * testMock.method(x)
  }

  static interface TestMock {
    void method(double x)
  }

  static class TestClass {
    double callMethod(TestMock mock) {
      double x = Math.random()
      mock.method(x)
      return x
    }
  }

代码工作正常,但测试失败并显示消息:

One or more arguments(s) didn't match:
0: argument == expected
   |        |  |
   |        |  null
   |        false
   0.5757686318956925

所以看起来 then 中的模拟检查是在 when 块中分配值之前完成的。

有没有办法让 Spock 在检查模拟调用之前分配这个值?或者我可以用不同的方式进行检查吗?

我唯一的想法是向该方法(或实际上向 class)注入一个 id-generator 并在测试中存根它,但这会使代码复杂化,我想避免它。

我根据 kriegaex 评论修改了代码示例。

修复示例代码

在开始之前,您的示例代码存在两个问题:

  • 1 * testMock(x) 应该是 1 * testMock.method(x)
  • callMethod 应该 return double,而不是 int,否则结果将始终为 0(0 和 1 之间的双精度数在转换时总是产生 0到一个整数)。

下次请确保您的示例代码不仅可以编译,而且可以执行预期的操作。示例代码只有在没有额外错误的情况下才有用,试图帮助您的人需要先修复这些错误,然后才能专注于实际问题。

手头的问题

正如您已经注意到的那样,交互,即使在 then: 块中按词法定义,也会通过 Spock 的编译器 AST 转换以这种方式进行转换,以便在初始化 mock 时将它们注册到 mock 上。这是必要的,因为模拟必须在调用 when: 块中的任何方法之前准备就绪。在已经执行 when: 块时尝试直接使用稍后才知道的结果,将导致您描述的问题。 先有鸡还是先有蛋?在这种情况下,您不能指定方法参数约束,使用约束中调用模拟方法的另一个方法的未来结果。

解决方法

一个可能的解决方法是对方法进行存根,并在计算存根结果的闭包中捕获参数,例如>> { args -> arg = args[0]; return "stubbed" }。当然,return关键字在Groovy中的闭包或方法的最后语句中是多余的。在您的情况下,该方法甚至是 void,因此在这种情况下您根本不需要 return 任何东西。

一个例子

我调整了您的示例代码,重命名了 类、方法和变量,以更清楚地描述哪些是什么以及正在发生什么:

package de.scrum_master.Whosebug.q72029050

import spock.lang.Specification

class InteractionOnCallResultTest extends Specification {
  def "dependency method is called with the expected argument"() {
    given:
    def dependency = Mock(Dependency)
    def randomNumber
    def dependencyMethodArg

    when:
    randomNumber = new UnderTest().getRandomNumber(dependency)

    then:
    1 * dependency.dependencyMethod(_) >> { args -> dependencyMethodArg = args[0] }
    dependencyMethodArg == randomNumber
  }
}
interface Dependency {
  void dependencyMethod(double x)
}
class UnderTest {
  double getRandomNumber(Dependency dependency) {
    double randomNumber = Math.random()
    dependency.dependencyMethod(randomNumber)
    return randomNumber
  }
}

Groovy Web Console 中尝试。