【问题标题】:Spock: How to mock a method that accepts a single byte[] parameter?Spock:如何模拟接受单个 byte[] 参数的方法?
【发布时间】:2016-05-17 02:40:04
【问题描述】:

让 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$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 的 Spock 文档中描述的行为。我选择性地抄录了以下相关内容:

如果闭包声明了一个无类型参数,它会通过方法的参数列表... 如果闭包声明了多个参数或单个类型参数,方法参数将一一映射到闭包参数...

我在上述规范中添加了一些其他测试,看看我是否理解这些陈述。完整的 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 几乎想将模拟方法视为 Bytes 的 varargs 方法并解压缩 byte[] 参数。我发现的唯一一个听起来有点像我的问题的报告问题是GROOVY-4843,它是针对内置的 Groovy 模拟框架提出的,并且没有解决方案。

有没有办法让测试 (4) 按预期运行?也就是说,能够在一个参数的闭包中使用类型化数组参数? 还是我坚持使用形式 (3) 并且必须从非类型化闭包参数中提取实际的方法参数?

【问题讨论】:

    标签: unit-testing groovy mocking spock


    【解决方案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 需要可变参数,因此当它获取参数列表时,它会用数组包装列表。因此,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
        }
    }
    

    让我们将 Set 替换为 List

    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 ] }。闭包知道输入参数是一个列表,所以它会获取它可以找到的第一个并使用它。

    【讨论】:

    • 感谢您抽出宝贵时间进行这项研究。它似乎与 GROOVY-4843 基本相同。我不熟悉 Spock 的历史,但我想知道它是否是从原始的 Groovy 测试/模拟框架分叉并继承了这个问题?对@PeterNiederwieser 来说是个好问题...
    猜你喜欢
    • 1970-01-01
    • 2017-12-29
    • 1970-01-01
    • 1970-01-01
    • 2010-11-20
    • 1970-01-01
    • 2023-03-08
    • 2021-12-23
    • 2020-05-23
    相关资源
    最近更新 更多