Может ли Mockito захватывать аргументы метода, вызываемого несколько раз?

446

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

Вот что я попробовал:

ArgumentCaptor<Foo> firstFooCaptor = ArgumentCaptor.forClass(Foo.class);
ArgumentCaptor<Foo> secondFooCaptor = ArgumentCaptor.forClass(Foo.class);
verify(mockBar).doSomething(firstFooCaptor.capture());
verify(mockBar).doSomething(secondFooCaptor.capture());
// then do some assertions on secondFooCaptor.getValue()

Но я получаю TooManyActualInvocationsисключение, так как Мокито считает, что его doSomethingследует вызывать только один раз.

Как я могу проверить аргумент второго вызова doSomething?

Эрик Уилсон
источник

Ответы:

784

Я думаю, что это должно быть

verify(mockBar, times(2)).doSomething(...)

Образец из мокито javadoc :

ArgumentCaptor<Person> peopleCaptor = ArgumentCaptor.forClass(Person.class);
verify(mock, times(2)).doSomething(peopleCaptor.capture());

List<Person> capturedPeople = peopleCaptor.getAllValues();
assertEquals("John", capturedPeople.get(0).getName());
assertEquals("Jane", capturedPeople.get(1).getName());
proactif
источник
3
Можете ли вы захватить аргументы, переданные doSomething()в каждом отдельном вызове с этим?
Мэтт б
36
Следует отметить, что в случае, если вы делаете что-то вроде этого: Person person = new Person("John"); doSomething(person); person.setName("Jane"); doSomething(person);захваченный аргумент будет дважды повторяться (потому что на самом деле это один и тот же объект person), поэтому capturedPeople.get(0).getName() == capturedPeople.get(1).getName() == "Jane"см. Также groups.google.com/forum/#!msg/mockito/ KBRocVedYT0 / 5HtARMl9r2wJ .
asmaier
2
Это хорошо, но как я могу проверить два разных типа вызовов объектов? Например, ExecutorService.submit (new MyRunableImpl ()); а затем ExecutorService.submit (новый MyAnotherRunableImpl ())?
Леон
Если нужно разобраться со случаем, описанным @asmaier, я разместил ответ здесь: stackoverflow.com/a/36574817/1466267
SpaceTrucker
1
Для тех, кто все еще задается вопросом об ответе на вопрос Леона, вы должны использовать общий базовый класс ( Runnable) и, если необходимо, выполнить более конкретную проверку типа для захваченного аргумента.
Матфей, ​​прочитанный
50

Начиная с Mockito 2.0 есть также возможность использовать статический метод Matchers.argThat (ArgumentMatcher) . С помощью Java 8 теперь стало намного чище и удобнее писать:

verify(mockBar).doSth(argThat((arg) -> arg.getSurname().equals("OneSurname")));
verify(mockBar).doSth(argThat((arg) -> arg.getSurname().equals("AnotherSurname")));

Если вы привязаны к более низкой версии Java, это тоже неплохо:

verify(mockBar).doSth(argThat(new ArgumentMatcher<Employee>() {
        @Override
        public boolean matches(Object emp) {
            return ((Employee) emp).getSurname().equals("SomeSurname");
        }
    }));

Конечно, никто из них не может проверить порядок звонков - для чего вы должны использовать InOrder :

InOrder inOrder = inOrder(mockBar);

inOrder.verify(mockBar).doSth(argThat((arg) -> arg.getSurname().equals("FirstSurname")));
inOrder.verify(mockBar).doSth(argThat((arg) -> arg.getSurname().equals("SecondSurname")));

Пожалуйста, взгляните на проект mockito-java8, который позволяет совершать такие звонки, как:

verify(mockBar).doSth(assertArg(arg -> assertThat(arg.getSurname()).isEqualTo("Surname")));
Мацей Добровольский
источник
2
Это хорошая техника. В настоящее время я получаю довольно загадочный вывод: "Требуется, но не вызывается: / n mockAppender.append (<Менеджер индекса ut $$ lambda $ 5 9/1 3 1 9 5 1 0 1 6>);" - Аргумент есть CharSequence. Знаете ли вы какой-нибудь способ заставить отчет правильно распечатать искомый аргумент?
Майк Грызун
@mikerodent Загадочный вывод может быть исправлен, если вы пойдете по более подробному пути создания класса, реализующего ArgumentMatcher <T>. Переопределение метода toString в вашей реализации предоставит любое сообщение, которое вы хотите в выводе теста mockito.
Ной Соломон
25

Если вы не хотите проверять все звонки doSomething(), только последний, вы можете просто использовать ArgumentCaptor.getValue(). Согласно сообщению Mockito Javadoc :

Если метод был вызван несколько раз, он возвращает последнее захваченное значение

Так что это будет работать (предполагается, что Fooесть метод getName()):

ArgumentCaptor<Foo> fooCaptor = ArgumentCaptor.forClass(Foo.class);
verify(mockBar, times(2)).doSomething(fooCaptor.capture());
//getValue() contains value set in second call to doSomething()
assertEquals("2nd one", fooCaptor.getValue().getName());
lreeder
источник
Есть ли способ захватить оба значения?
Харс
9

Вы также можете использовать аннотированный @Captor ArgumentCaptor. Например:

@Mock
List<String> mockedList;

@Captor
ArgumentCaptor<String> argCaptor;

@BeforeTest
public void init() {
    //Initialize objects annotated with @Mock, @Captor and @Spy.
    MockitoAnnotations.initMocks(this);
}

@Test
public void shouldCallAddMethodTwice() {
    mockedList.add("one");
    mockedList.add("two");
    Mockito.verify(mockedList, times(2)).add(argCaptor.capture());

    assertEquals("one", argCaptor.getAllValues().get(0));
    assertEquals("two", argCaptor.getAllValues().get(1));
}
Михал Стохмаль
источник
6

С лямбдами Java 8 удобным способом является использование

org.mockito.invocation.InvocationOnMock

when(client.deleteByQuery(anyString(), anyString())).then(invocationOnMock -> {
    assertEquals("myCollection", invocationOnMock.getArgument(0));
    assertThat(invocationOnMock.getArgument(1), Matchers.startsWith("id:"));
}
Антон Середкин
источник
Я не могу понять, как это удобнее, чем по-старому. Я люблю хорошо использовать лямбды, но я не уверен, что это один из них.
Эрик Уилсон
0

Прежде всего: вы всегда должны импортировать mockito static, таким образом, код будет намного более читабельным (и интуитивно понятным) - примеры кода ниже требуют его работы:

import static org.mockito.Mockito.*;

В методе verify () вы можете передать ArgumentCaptor для обеспечения выполнения в тесте и ArgumentCaptor для оценки аргументов:

ArgumentCaptor<MyExampleClass> argument = ArgumentCaptor.forClass(MyExampleClass.class);
verify(yourmock, atleast(2)).myMethod(argument.capture());

List<MyExampleClass> passedArguments = argument.getAllValues();

for (MyExampleClass data : passedArguments){
    //assertSometing ...
    System.out.println(data.getFoo());
}

Список всех переданных аргументов во время теста доступен через метод arguments.getAllValues ​​().

Значение единственного (последнего вызванного) аргумента доступно через arguments.getValue () для дальнейшей манипуляции / проверки или всего, что вы хотите сделать.

fl0w
источник