使用在 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 中尝试。
我有一个方法,其中 生成一个标识符,将它发送到一些外部依赖项,然后 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
应该 returndouble
,而不是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 中尝试。