2016-05-17 96 views
0

我遇到了一个问题,让Spock嘲笑一个接受单个byte[]作为参数的方法。Spock:如何模拟接受单字节[]参数的方法?

被以相同的方式未能我的生产代码的简单的玩具实例如下:

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 '[[[email protected]]' 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$1.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) 

我依托于斯波克文档描述的行为Computing Return Values“基于交互的测试”。我选择转录如下相关位:

如果封闭声明了一个无类型参数,如果封闭宣布一个以上的参数或单一类型化的参数,它就会通过这个方法的参数列表... ,方法参数将逐个映射到关闭参数...

我在上面的规范中添加了一些其他测试,看看我是否理解了这些语句。完整的MCVE如下:

$ gradle这个--version

------------------------------------------------------------ 
Gradle 2.13 
------------------------------------------------------------ 

Build time: 2016-04-25 04:10:10 UTC 
Build number: none 
Revision:  3b427b1481e7303c90be7b05079b05b1c 

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 /测试/常规/ 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)并且必须从无类型的闭包参数中提取实际的方法参数?

回答

1

简短的回答:没有正常的方法来做到这一点,因为它是一个错误。只有黑客&技巧。

下面是解释:您的闭包在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返回参数列表。

public static <T> T invokeClosure(Closure<T> closure, Object... args) 

invokeClosure需要varargs,这样当它获取参数列表时,它会用数组包装列表。因此,spock将Object [] {ArrayList [byte []]}传递给闭包。同时,闭包知道它接受可变参数(因为你声明了byte []),所以它期望Object [{byte []}]被传递。这里我们得到了例外。我相信这是一个bug,spock不需要用JavaMockInterceptor :: intercept中的数组包装所有参数。

PS:还有一个有趣的bug

这一个正常工作

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]] 

你可以看到相反列表<整数>我们获得列表<列表<整数>>。 要理解它为什么会发生,让我们回到传递的参数。 在这种情况下,它看起来像Object [] {ArrayList [ArrayList]}。 Closure知道输入参数是一个列表,因此它会首先找到并使用它。

+0

感谢您花时间做这项研究。它看起来与GROOVY-4843基本相同。我对Spock的历史并不熟悉,但是我不知道它是从原始的Groovy测试/嘲弄框架中分离出来的,并且继承了这个问题?对于@PeterNiederwieser会是一个好问题... –