2013-06-20 5 views
21

Чтобы проверить количество взаимодействий с макетом, где параметр в вызове метода является определенного типа, можно сделатьMockito проверить взаимодействие с ArgumentCaptor

mock.someMethod(new FirstClass()); 
mock.someMethod(new OtherClass()); 
verify(mock, times(1)).someMethod(isA(FirstClass.class)); 

Это будет проходить благодаря вызову isA так someMethod был вызван дважды, но только один раз с аргументом FirstClass

Однако эта модель, кажется, не представляется возможным при использовании ArgumentCaptor, даже если Captor был создан для конкретного аргумента FirstClass

это не работает

mock.someMethod(new FirstClass()); 
mock.someMethod(new OtherClass()); 
ArgumentCaptor<FirstClass> captor = ArgumentCaptor.forClass(FirstClass.class); 
verify(mock, times(1)).someMethod(captor.capture()); 

он говорит, что макет был вызван более чем один раз.

Есть ли способ выполнить эту проверку при захвате аргумента для дальнейшей проверки?

ответ

24

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

verify(mock, times(1)).someMethod(argThat(personNamed("Bob"))); 

Matcher<Person> personNamed(final String name) { 
    return new TypeSafeMatcher<Person>() { 
     public boolean matchesSafely(Person item) { 
      return name.equals(item.getName()); 
     } 
     public void describeTo(Description description) { 
      description.appendText("a Person named " + name); 
     } 
    }; 
} 

Matchers обычно приводит к более считываемые тестам и более полезные сообщения об ошибках теста. Они также имеют тенденцию быть очень многоразовыми, и вы обнаружите, что создаете библиотеку, предназначенную для тестирования вашего проекта. Наконец, вы также можете использовать их для нормальных утверждений об испытаниях с использованием Assert.assertThat() JUnit, поэтому вы можете использовать их в двух вариантах.

+3

Это действительно не отвечает на вопрос OP, не так ли? – fge

+9

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

+0

Это самое элегантное решение, и я фактически использовал – Hilikus

5

Цитирование документы:

Обратите внимание, что ArgumentCaptorне делать каких-либо проверок типа, только там, чтобы избежать литья в вашем коде. Однако это может измениться (могут быть добавлены проверки типа ) в будущем крупном выпуске.

Для этого я бы не использовал ArgumentCaptor. Этот класс захватывает (буквально) все, несмотря на то, что класс был предоставлен, так как это аргумент .forClass.

Чтобы достичь того, чего вы хотите, я предлагаю перехватывать аргумент, используя интерфейс Mockito в Answer:

private FirstClass lastArgument; 

@Test 
public void captureFirstClass() throws Exception { 
    doAnswer(captureLastArgument()).when(mock).someMethod(anInstanceOfFirstClass()); 
    mock.someMethod(new FirstClass()); 
    mock.someMethod(new OtherClass()); 

    verify(mock, times(1)).someMethod(anInstanceOfFirstClass()); 
    //write your desired matchers against lastArgument object 
} 

private Answer<FirstClass> captureLastArgument() { 
    return new Answer<FirstClass>() { 
     @Override 
     public FirstClass answer(InvocationOnMock invocation) throws Throwable { 
      TestClass.this.lastArgument = (FirstClass) invocation.getArguments()[0]; 
      return null; 
     } 
    }; 
} 

private static Object anInstanceOfFirstClass(){ 
    return Mockito.argThat(isA(FirstClass.class)); 
} 
+0

+1, это правильное решение – fge

+0

Есть двумя способами это можно было бы упростить. Просто используйте Mockito 'isA', а не Hamcrest. Тогда вам не нужно вызывать 'argThat', и у вас, конечно же, не должен быть дополнительный метод для этого. Кроме того, вам никогда не нужно писать 'times (1)'. Одноразовая проверка - по умолчанию Mockito - просто опустите второй аргумент 'verify'. –

+1

Спасибо, я не знал, что у mockito была собственная версия 'isA', которую можно было использовать. Но я все же предпочитаю четко указывать количество ожидаемых вызовов метода, используя 'times (1)' или 'only()', для лучшей читаемости (IMO). –

2

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

// given 
    ArgumentCaptor<AA> captor = ArgumentCaptor.forClass(AA.class); 
    CC cc = new CC(); 
    // when 
    cut.someMethod(new AA()); 
    cut.someMethod(new BB()); 
    cut.someMethod(new BB()); 
    cut.someMethod(cc); 
    // then 
    Mockito.verify(collaborator, atLeastOnce()).someMethod(captor.capture()); 
    Mockito.verify(collaborator, times(1)).someMethod(isA(AA.class)); 
    Mockito.verify(collaborator, times(2)).someMethod(isA(BB.class)); 
    Mockito.verify(collaborator, times(1)).someMethod(isA(CC.class)); 
    assertEquals(cc, captor.getValue()); 

По-видимому, общий тип ссылки на улавливатель не влияет ни на что во время выполнения.

+2

Напоминание: 'capture()' захватывает все аргументы, а 'captor.getValue()' возвращает последний, который был использован, независимо от класса, переданного в качестве аргумента в 'ArgumentCaptor.forClass' –

+0

. Это простейшее решение. Он отлично работает **, если ** держит комментарий @MarlonBernardes. +1 – Hilikus

0

Я также столкнулся с этой проблемой сегодня. Я думал, что могу просто сделать что-то вроде

verify(mock).someMethod(and(isA(FirstClass.class), captor.capture())); 

но я не мог заставить его работать.Я закончил с этим решением:

@Test 
public void Test() throws Exception { 
    final ArgumentCaptor<FirstClass> captor = ArgumentCaptor.forClass(FirstClass.class); 

    mock.someMethod(new FirstClass()); 
    mock.someMethod(new OtherClass()); 

    verify(eventBus, atLeastOnce()).post(captor.capture()); 
    final List<FirstClass> capturedValues = typeCheckedValues(captor.getAllValues(), FirstClass.class); 
    assertThat(capturedValues.size(), is(1)); 
    final FirstClass capturedValue = capturedValues.get(0); 
    // Do assertions on capturedValue 
} 

private static <T> List<T> typeCheckedValues(List<T> values, Class<T> clazz) { 
    final List<T> typeCheckedValues = new ArrayList<>(); 
    for (final T value : values) { 
     if (clazz.isInstance(value)) { 
      typeCheckedValues.add(value); 
     } 
    } 
    return typeCheckedValues; 
} 

Примечание: если необходимо захваченный таким образом typeCheckedValues только один класс может быть упрощена в:

private static List<FirstClass> typeCheckedValues(List<FirstClass> values) { 
    final List<FirstClass> typeCheckedValues = new ArrayList<>(); 
    for (final Object value : values) { 
     if (value instanceof FirstClass) { 
      typeCheckedValues.add((FirstClass) value); 
     } 
    } 
    return typeCheckedValues; 
} 
Смежные вопросы