2016-05-17 3 views
0

У меня возникла проблема с получением Спока, чтобы высмеять метод, который принимает параметр 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 '[[[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) 

я полагаться на поведении, описанном в документации Spock для 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 хотят обработать метод издевательства как метод varargs Byte и распаковать аргумент 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 ожидает с переменным числом аргументов, так что, когда он получает список аргументов он оборачивает список с массивом.Следовательно, spock передает Object [] {ArrayList [byte []]} в закрытие. Между тем, закрытие знает, что оно принимает varargs (поскольку вы объявляете байт []), поэтому он ожидает, что 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]] 

Вы можете увидеть что вместо Lis t < Целое> мы получаем Список < Список < Целое >>. Чтобы понять, почему это происходит, вернемся к пройденным аргументам. В этом случае это выглядит как Object [] {ArrayList [ArrayList]}. Closure знает, что входной аргумент представляет собой список, поэтому он берет первый, который он может найти и использует.

+0

Спасибо, что нашли время для этого исследования. Казалось бы, это в основном та же проблема, что и GROOVY-4843. Я не знаком с историей Спока, но мне интересно, разве он был раздвоен из первоначальной структуры Groovy/насмешливой структуры и унаследовал эту проблему? Будет хорошим вопросом для @PeterNiederwieser ... –