Spock:如何模拟接受单个 byte[] 参数的方法?
Spock: How to mock a method that accepts a single byte[] parameter?
我在让 Spock 模拟接受单个 byte[]
作为参数的方法时遇到问题。
一个简单的玩具示例以与我的生产代码相同的方式失败如下:
import java.util.function.Consumer
import spock.lang.Specification
class ConsumerSpec extends Specification {
// ... elided ...
def '4: parameter is of an array type using single typed argument'() {
given:
def consumer = Mock(Consumer)
when:
consumer.accept([20, 21] as byte[])
then:
consumer.accept(_) >> { byte[] arg ->
assert arg[0] == 20
assert arg[1] == 21
}
}
// ... elided ...
}
失败消息是
ConsumerSpec > 4: parameter is of an array type using single typed argument FAILED
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '[[B@43562196]' with class 'java.util.Arrays$ArrayList' to class 'java.lang.Byte'
at groovy.lang.Closure.call(Closure.java:423)
at org.spockframework.mock.response.CodeResponseGenerator.invokeClosure(CodeResponseGenerator.java:53)
at org.spockframework.mock.response.CodeResponseGenerator.doRespond(CodeResponseGenerator.java:36)
at org.spockframework.mock.response.SingleResponseGenerator.respond(SingleResponseGenerator.java:31)
at org.spockframework.mock.response.ResponseGeneratorChain.respond(ResponseGeneratorChain.java:45)
at org.spockframework.mock.runtime.MockInteraction.accept(MockInteraction.java:76)
at org.spockframework.mock.runtime.MockInteractionDecorator.accept(MockInteractionDecorator.java:46)
at org.spockframework.mock.runtime.InteractionScope.accept(InteractionScope.java:41)
at org.spockframework.mock.runtime.MockController.handle(MockController.java:39)
at org.spockframework.mock.runtime.JavaMockInterceptor.intercept(JavaMockInterceptor.java:72)
at org.spockframework.mock.runtime.DynamicProxyMockInterceptorAdapter.invoke(DynamicProxyMockInterceptorAdapter.java:28)
at ConsumerSpec.4: parameter is of an array type using single typed argument(ConsumerSpec.groovy:52)
我依赖于 Spock 文档中针对 Computing Return Values 在基于交互的测试下描述的行为。我有选择地转录了以下相关位:
If the closure declares a single untyped parameter, it gets passed the method’s argument list...
If the closure declares more than one parameter or a single typed parameter, method arguments will be mapped one-by-one to closure parameters...
我在上面的规范中添加了一些其他测试,看看我是否理解这些陈述。完整的 MCVE 如下:
$gradle --version
------------------------------------------------------------
Gradle 2.13
------------------------------------------------------------
Build time: 2016-04-25 04:10:10 UTC
Build number: none
Revision: 3b427b1481e46232107303c90be7b05079b05b1c
Groovy: 2.4.4
Ant: Apache Ant(TM) version 1.9.6 compiled on June 29 2015
JVM: 1.8.0_91 (Oracle Corporation 25.91-b14)
OS: Linux 4.4.8-300.fc23.x86_64 amd64
// build.gradle
plugins {
id 'groovy'
}
repositories {
mavenCentral()
}
dependencies {
testCompile(
[group: 'org.spockframework', name: 'spock-core', version: '1.0-groovy-2.4']
)
}
// src/test/groovy/ConsumerSpec.groovy
import java.util.function.Consumer
import spock.lang.Specification
class ConsumerSpec extends Specification {
def '1: parameter is of a non-array type using single untyped argument'() {
given:
def consumer = Mock(Consumer)
when:
consumer.accept('value')
then:
consumer.accept(_) >> { args ->
String arg = args[0]
assert arg == 'value'
}
}
def '2: parameter is of a non-array type using single typed argument'() {
given:
def consumer = Mock(Consumer)
when:
consumer.accept('value')
then:
consumer.accept(_) >> { String arg ->
assert arg == 'value'
}
}
def '3: parameter is of an array type using single untyped argument'() {
given:
def consumer = Mock(Consumer)
when:
consumer.accept([20, 21] as byte[])
then:
consumer.accept(_) >> { args ->
byte[] arg = args[0]
assert arg[0] == 20
assert arg[1] == 21
}
}
def '4: parameter is of an array type using single typed argument'() {
given:
def consumer = Mock(Consumer)
when:
consumer.accept([20, 21] as byte[])
then:
consumer.accept(_) >> { byte[] arg ->
assert arg[0] == 20
assert arg[1] == 21
}
}
def '5: parameter is of an array type without using Mock'() {
given:
def consumer = { byte[] arg ->
assert arg[0] == 20
assert arg[1] == 21
} as Consumer<byte[]>
expect:
consumer.accept([20, 21] as byte[])
}
}
再一次,唯一失败的测试是 (4)。
根据失败消息,似乎 Spock 或 Groovy 想将模拟方法视为 Byte
s 的可变参数方法,并且正在解压缩 byte[]
参数。我发现的唯一一个听起来有点像我的问题的报告问题是 GROOVY-4843,它是针对内置 Groovy 模拟框架提交的,但没有解决。
有没有办法让测试 (4) 像预期的那样运行?也就是说,为了能够在一个参数的闭包中使用类型化数组参数? 或者我是否坚持使用形式 (3) 并且必须从非类型化闭包参数中提取实际方法参数?
简短的回答:没有正常的方法可以做到这一点,因为它是一个错误。只有黑客和技巧。
解释如下:您的闭包是在 CodeResponseGenerator::invokeClosure 中调用的。
private Object invokeClosure(IMockInvocation invocation) {
Class<?>[] paramTypes = code.getParameterTypes();
if (paramTypes.length == 1 && paramTypes[0] == IMockInvocation.class) {
return GroovyRuntimeUtil.invokeClosure(code, invocation);
}
code.setDelegate(invocation);
code.setResolveStrategy(Closure.DELEGATE_FIRST);
return GroovyRuntimeUtil.invokeClosure(code, invocation.getArguments());
}
invocation.getArguments return 参数列表。
public static <T> T invokeClosure(Closure<T> closure, Object... args)
invokeClosure 需要可变参数,因此当它获取参数列表时,它会用数组包装列表。因此,spock 将 Object[] { ArrayList [ byte[] ] } 传递给闭包。同时,闭包知道它接受可变参数(当你声明 byte[] 时),所以它期望 Object[ {byte[]} ] 被传递。在这里我们得到了例外。我相信这是一个错误,spock 不必用 JavaMockInterceptor::intercept.
中的数组包装所有参数
PS:又一个有趣的错误
这个很好用
def test() {
given:
Consumer<Set<Integer>> consumer = Mock()
when:
consumer.accept([1,2,3] as Set)
then:
consumer.accept(_) >> { Set<Integer> integer ->
assert integer.size() == 3
}
}
让我们用列表替换集合
def test() {
given:
Consumer<List<Integer>> consumer = Mock()
when:
consumer.accept([1,2,3])
then:
consumer.accept(_) >> { List<Integer> integer ->
assert integer.size() == 3
}
}
我们得到
integer.size() == 3
| | |
| 1 false
[[1, 2, 3]]
您可以看到,我们得到的不是 List,而是 List>。
为了理解为什么会发生这种情况,让我们回到传递的参数。
在这种情况下,它看起来像 Object[] { ArrayList [ ArrayList ] }。 Closure 知道输入参数是一个列表,所以它会使用它能找到的第一个参数。
我在让 Spock 模拟接受单个 byte[]
作为参数的方法时遇到问题。
一个简单的玩具示例以与我的生产代码相同的方式失败如下:
import java.util.function.Consumer
import spock.lang.Specification
class ConsumerSpec extends Specification {
// ... elided ...
def '4: parameter is of an array type using single typed argument'() {
given:
def consumer = Mock(Consumer)
when:
consumer.accept([20, 21] as byte[])
then:
consumer.accept(_) >> { byte[] arg ->
assert arg[0] == 20
assert arg[1] == 21
}
}
// ... elided ...
}
失败消息是
ConsumerSpec > 4: parameter is of an array type using single typed argument FAILED
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '[[B@43562196]' with class 'java.util.Arrays$ArrayList' to class 'java.lang.Byte'
at groovy.lang.Closure.call(Closure.java:423)
at org.spockframework.mock.response.CodeResponseGenerator.invokeClosure(CodeResponseGenerator.java:53)
at org.spockframework.mock.response.CodeResponseGenerator.doRespond(CodeResponseGenerator.java:36)
at org.spockframework.mock.response.SingleResponseGenerator.respond(SingleResponseGenerator.java:31)
at org.spockframework.mock.response.ResponseGeneratorChain.respond(ResponseGeneratorChain.java:45)
at org.spockframework.mock.runtime.MockInteraction.accept(MockInteraction.java:76)
at org.spockframework.mock.runtime.MockInteractionDecorator.accept(MockInteractionDecorator.java:46)
at org.spockframework.mock.runtime.InteractionScope.accept(InteractionScope.java:41)
at org.spockframework.mock.runtime.MockController.handle(MockController.java:39)
at org.spockframework.mock.runtime.JavaMockInterceptor.intercept(JavaMockInterceptor.java:72)
at org.spockframework.mock.runtime.DynamicProxyMockInterceptorAdapter.invoke(DynamicProxyMockInterceptorAdapter.java:28)
at ConsumerSpec.4: parameter is of an array type using single typed argument(ConsumerSpec.groovy:52)
我依赖于 Spock 文档中针对 Computing Return Values 在基于交互的测试下描述的行为。我有选择地转录了以下相关位:
If the closure declares a single untyped parameter, it gets passed the method’s argument list... If the closure declares more than one parameter or a single typed parameter, method arguments will be mapped one-by-one to closure parameters...
我在上面的规范中添加了一些其他测试,看看我是否理解这些陈述。完整的 MCVE 如下:
$gradle --version
------------------------------------------------------------
Gradle 2.13
------------------------------------------------------------
Build time: 2016-04-25 04:10:10 UTC
Build number: none
Revision: 3b427b1481e46232107303c90be7b05079b05b1c
Groovy: 2.4.4
Ant: Apache Ant(TM) version 1.9.6 compiled on June 29 2015
JVM: 1.8.0_91 (Oracle Corporation 25.91-b14)
OS: Linux 4.4.8-300.fc23.x86_64 amd64
// build.gradle
plugins {
id 'groovy'
}
repositories {
mavenCentral()
}
dependencies {
testCompile(
[group: 'org.spockframework', name: 'spock-core', version: '1.0-groovy-2.4']
)
}
// src/test/groovy/ConsumerSpec.groovy
import java.util.function.Consumer
import spock.lang.Specification
class ConsumerSpec extends Specification {
def '1: parameter is of a non-array type using single untyped argument'() {
given:
def consumer = Mock(Consumer)
when:
consumer.accept('value')
then:
consumer.accept(_) >> { args ->
String arg = args[0]
assert arg == 'value'
}
}
def '2: parameter is of a non-array type using single typed argument'() {
given:
def consumer = Mock(Consumer)
when:
consumer.accept('value')
then:
consumer.accept(_) >> { String arg ->
assert arg == 'value'
}
}
def '3: parameter is of an array type using single untyped argument'() {
given:
def consumer = Mock(Consumer)
when:
consumer.accept([20, 21] as byte[])
then:
consumer.accept(_) >> { args ->
byte[] arg = args[0]
assert arg[0] == 20
assert arg[1] == 21
}
}
def '4: parameter is of an array type using single typed argument'() {
given:
def consumer = Mock(Consumer)
when:
consumer.accept([20, 21] as byte[])
then:
consumer.accept(_) >> { byte[] arg ->
assert arg[0] == 20
assert arg[1] == 21
}
}
def '5: parameter is of an array type without using Mock'() {
given:
def consumer = { byte[] arg ->
assert arg[0] == 20
assert arg[1] == 21
} as Consumer<byte[]>
expect:
consumer.accept([20, 21] as byte[])
}
}
再一次,唯一失败的测试是 (4)。
根据失败消息,似乎 Spock 或 Groovy 想将模拟方法视为 Byte
s 的可变参数方法,并且正在解压缩 byte[]
参数。我发现的唯一一个听起来有点像我的问题的报告问题是 GROOVY-4843,它是针对内置 Groovy 模拟框架提交的,但没有解决。
有没有办法让测试 (4) 像预期的那样运行?也就是说,为了能够在一个参数的闭包中使用类型化数组参数? 或者我是否坚持使用形式 (3) 并且必须从非类型化闭包参数中提取实际方法参数?
简短的回答:没有正常的方法可以做到这一点,因为它是一个错误。只有黑客和技巧。
解释如下:您的闭包是在 CodeResponseGenerator::invokeClosure 中调用的。
private Object invokeClosure(IMockInvocation invocation) {
Class<?>[] paramTypes = code.getParameterTypes();
if (paramTypes.length == 1 && paramTypes[0] == IMockInvocation.class) {
return GroovyRuntimeUtil.invokeClosure(code, invocation);
}
code.setDelegate(invocation);
code.setResolveStrategy(Closure.DELEGATE_FIRST);
return GroovyRuntimeUtil.invokeClosure(code, invocation.getArguments());
}
invocation.getArguments return 参数列表。
public static <T> T invokeClosure(Closure<T> closure, Object... args)
invokeClosure 需要可变参数,因此当它获取参数列表时,它会用数组包装列表。因此,spock 将 Object[] { ArrayList [ byte[] ] } 传递给闭包。同时,闭包知道它接受可变参数(当你声明 byte[] 时),所以它期望 Object[ {byte[]} ] 被传递。在这里我们得到了例外。我相信这是一个错误,spock 不必用 JavaMockInterceptor::intercept.
中的数组包装所有参数PS:又一个有趣的错误
这个很好用
def test() {
given:
Consumer<Set<Integer>> consumer = Mock()
when:
consumer.accept([1,2,3] as Set)
then:
consumer.accept(_) >> { Set<Integer> integer ->
assert integer.size() == 3
}
}
让我们用列表替换集合
def test() {
given:
Consumer<List<Integer>> consumer = Mock()
when:
consumer.accept([1,2,3])
then:
consumer.accept(_) >> { List<Integer> integer ->
assert integer.size() == 3
}
}
我们得到
integer.size() == 3
| | |
| 1 false
[[1, 2, 3]]
您可以看到,我们得到的不是 List>。
为了理解为什么会发生这种情况,让我们回到传递的参数。
在这种情况下,它看起来像 Object[] { ArrayList [ ArrayList ] }。 Closure 知道输入参数是一个列表,所以它会使用它能找到的第一个参数。