2015-10-26 2 views
2

Этот 2013 post on SO спросил, как использовать сочетания Hamcrest для проверки списков/коллекций в Mockito. Принятое решение состояло в том, чтобы бросить Матчи в (Сборник).Сокеты Mockito/JMockit & Hamcrest: как проверить списки/коллекции?

Я пытаюсь сделать что-то подобное, но столкнулся с ошибкой класса. Я не уверен, что я злоупотребляю состязаниями Hamcrest, или если это использование просто не поддерживается Mockito. В моем случае, я пытаюсь использовать список Matchers как мой аргумент:

static class Collaborator 
{ 
    void doSomething(Iterable<String> values) {} 
} 

@Test 
public void usingMockito() 
{ 
    Collaborator mock = Mockito.mock(Collaborator.class); 
    mock.doSomething(Arrays.asList("a", "b")); 

    // legal cast 
    Mockito.verify(mock).doSomething((Collection<String>)argThat(Matchers.contains("a", "b"))); 
    // legal cast 
    Mockito.verify(mock).doSomething((Collection<String>)argThat(Matchers.contains(Matchers.equalTo("a"), Matchers.equalTo("b")))); 

    // illegal cast!!! Cannot cast from Iterable<capture#3-of ? extends List<Matcher<String>>> to Collection<String> 
    Mockito.verify(mock).doSomething((Collection<String>)argThat(Matchers.contains(Arrays.asList(Matchers.equalTo("a"), Matchers.equalTo("b"))))); 
} 

Но я получаю ошибку произнесения:

Cannot cast from Iterable<capture#3-of ? extends List<Matcher<String>>> to Collection<String> 

я делаю что-то не поддерживается?

ответ

4

Как Джефф Боуман уже отмечалось, проблема заключается в том, что компилятор не знает, какой из 4 contains методов, которые вы пытаетесь вызвать.

Список вы строите

Arrays.asList(Matchers.equalTo("a"), Matchers.equalTo("b")) 

имеет тип

List<Matcher<String>> 

но метод contains вы хотите позвонить (<E> Matcher<Iterable<? extends E>> contains(List<Matcher<? super E>> itemMatchers)) ожидает предустановленный вариант

List<Matcher<? super String>> 

в качестве параметра. В списке ваш тип не соответствует ожидаемому, компилятор на самом деле думает, что вы пытаетесь вызвать

<E> Matcher<Iterable<? extends E>> contains(E... items) 

Решение: дать компилятору, что он хочет. Создать List<Matcher<? super String>> вместо List<Matcher<String>>:

 List<Matcher<? super String>> matchersList = new ArrayList<>(); 
     matchersList.add(Matchers.equalTo("a")); 
     matchersList.add(Matchers.equalTo("b")); 

     // no illegal cast anymore 
     Mockito.verify(mock).doSomething(
      (Collection<String>) argThat(Matchers.contains(matchersList))); 

EDIT:

Добавление встроенного решения Джефф Боумена из его комментария, что позволяет использовать Arrays.asList, как указано в вопросе:

 Mockito.verify(mock).doSomething(
       (Collection<String>) argThat(
         Matchers.contains(
           Arrays.<Matcher<? super String>> asList(Matchers.equalTo("a"), Matchers.equalTo("b"))))); 
+0

Спасибо за выноску! +1, и вы также можете сделать это inline путем параметризации 'asList' (т. Е.' Arrays. > asList (...) '). –

+0

@JeffBowman отлично. Я добавлю это к ответу, надеюсь, что вы не возражаете – Ruben

+0

Прошу! У меня не было возможности проверить это, но вы сформулировали очень хороший ответ с очень конкретным объяснением, и определенно заслуживают должного внимания. –

2

Я считаю, что это связано с раздражающим неоднозначности в Hamcrest, которая имеет на своей Matchers class:

  1. <E> Matcher<Iterable<? extends E>> contains(E... items)
  2. <E> Matcher<Iterable<? extends E>> contains(Matcher<? super E> itemMatcher)
  3. <E> Matcher<Iterable<? extends E>> contains(Matcher<? super E>... itemMatchers)
  4. <E> Matcher<Iterable<? extends E>> contains(List<Matcher<? super E>> itemMatchers)

Это верно, в зависимости от того, вы передаете Hamcrest элемент, совпадение, массив шаблонов varargs или список совпадений, вы получите другое поведение. Поскольку у Java нет отвращения к сопоставлениям списков совпадений Hamcrest, есть много возможностей для одного утверждения, чтобы соответствовать более одной из этих перегрузок, а выбор между ними является наиболее конкретной перегрузкой, определяемой головокружительной алгеброй типов в JLS 18.5.4.

Я думаю, что вы намерены пункт # 4 выше обводной contains список Matcher<E> (Matcher<String>) и получить обратно Matcher<Iterable<? extends String>> бут компилятор видит его как # 1 проход containsзначение типа E (List<Matcher<String>>) и верните Matcher<Iterable<? extends List<Matcher<String>>>>.

Есть несколько обходных путей, которые я еще не испытанные:

  • Извлечение Matcher переменной, которую вы можете сделать с Hamcrest matchers как contains но не Mockito matchers как argThat:

    Matcher<Iterable<String>> matchesAAndB = Matchers.contains(
        Arrays.asList(Matchers.equalTo("a"), Matchers.equalTo("b"))); 
    Mockito.verify(mock).doSomething((Collection<String>)argThat(matchesAAndB)); 
    
  • Выберите E явно:

    Mockito.verify(mock).doSomething((Collection<String>)argThat(
        Matchers.<String>contains(Arrays.asList(
         Matchers.equalTo("a"), Matchers.equalTo("b"))))); 
    
+0

Ничего себе ... Я бы не подумал об этом. Я не понимал, что компилятор будет пропускать это из-за проблемы с перегрузкой. Я проведу несколько быстрых тестов и выясню, что является правильным утверждением. Спасибо. –

+0

Оказывается, перегрузка не является причиной проблемы. Мы можем видеть, что добавив два тестовых метода к тестовому классу и используя их вместо совпадений Hamcrest: 'static Matcher > hasItems (T ... items) {return null; } 'и' static Matcher > содержит (T ... items) {return null; } ', где первая с компилятором' argThat' отлично, вторая - нет. Настоящей причиной является использование '? расширяет T' во втором совпадении. –

+0

Да, это хорошее резюме: первостепенная причина - несовместимые ограниченные дженерики. Пожалуйста, прочитайте сообщение об ошибке OP, и вы увидите, почему перегрузки являются запутанной частью симптомов. –

2

лучшим способом является использование стандартного метода assertThat (от Hamcrest или JUnit), который будет работать лучше всего с любым соглашением Hamcrest. С JMockit вы могли бы сделать:

@Test 
public void usingJMockit(@Mocked final Collaborator mock) { 
    mock.doSomething(asList("a", "b")); 

    new Verifications() {{ 
     List<String> values; 
     mock.doSomething(values = withCapture()); 

     // Now check the list of captured values using JUnit/Hamcrest: 
     assertThat(values, contains("a", "b")); 

     // Alternatively, could have used Asser4J, FEST Assert, etc. 
    }}; 
} 
+0

Аналогом Mockito является [ArgumentCaptor] (http://docs.mockito.googlecode.com/hg/org/mockito/ArgumentCaptor.html), для которого общие захваты могут быть более-легко выражены с помощью '' @ Captor' ' (http://docs.mockito.googlecode.com/hg/org/mockito/Captor.html). Опасайтесь, что вам, возможно, придется больше работать, чтобы игнорировать метод несочетания, так как функция 'verify' выполняется автоматически. –

+0

@JeffBowman JMockit 'withCapture()' эквивалентен Mackito 'ArgumentCaptor', я считаю; они оба поддерживают общие типы.Я не уверен, что вы имели в виду под «работать сложнее, чтобы игнорировать вызовы методов не совпадающих»; не то же самое с обоими API? –

+0

Вопрос имеет тег Mockito, а не JMockit, поэтому я предлагал эквивалент для акита. Я полагаю, что оба решения для захвата одинаковы, но _compared к решению, в котором вы передаете Matcher в Mockito_, любое захватывающее решение становится сложным _, когда есть несколько вызовов одного и того же метода. Тогда ваш захватчик будет иметь несколько значений для проверки, а не только один, и вам нужно будет утверждать, что _any_ из них совпадают. –

1

Я бы предпочел использовать allOf

import static org.hamcrest.Matchers.allOf; 
import static org.hamcrest.Matchers.equalTo; 
import static org.hamcrest.Matchers.hasItems; 
import static org.hamcrest.Matchers.hasProperty; 
import static org.hamcrest.Matchers.hasSize; 
import static org.hamcrest.Matchers.notNullValue; 
import static org.hamcrest.Matchers.nullValue; 

... 

    Mockito.verify(mock).doSomething(
     argThat(
      allOf(
       hasItems(equalTo("a")), 
       hasItems(equalTo("b")) 
      ) 
     ) 
    ); 
Смежные вопросы