O Mockito pode capturar argumentos de um método chamado várias vezes?

446

Eu tenho um método que é chamado duas vezes e quero capturar o argumento da segunda chamada de método.

Aqui está o que eu tentei:

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()

Mas eu recebo uma TooManyActualInvocationsexceção, como Mockito acha que doSomethingdeve ser chamado apenas uma vez.

Como posso verificar o argumento da segunda chamada de doSomething?

Eric Wilson
fonte

Respostas:

783

Eu acho que deveria ser

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

Amostra de mockito 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());
proativo
fonte
3
Você pode capturar os argumentos passados doSomething()em cada invocação separada com isso?
matt b
36
Deve-se observar que, no caso de você fazer algo assim: Person person = new Person("John"); doSomething(person); person.setName("Jane"); doSomething(person);o argumento capturado será o mesmo duas vezes (porque na verdade é o mesmo objeto de pessoa), portanto capturedPeople.get(0).getName() == capturedPeople.get(1).getName() == "Jane", consulte também groups.google.com/forum/#!msg/mockito/ KBRocVedYT0 / 5HtARMl9r2wJ .
Asmaier
2
Isso é bom, mas como posso testar duas invocações de objetos de tipos diferentes? Por exemplo ExecutorService.submit (new MyRunableImpl ()); e, em seguida, ExecutorService.submit (new MyAnotherRunableImpl ())?
Leon
Se a pessoa precisa lidar com o caso descrito por @asmaier, eu postei uma resposta aqui: stackoverflow.com/a/36574817/1466267
SpaceTrucker
1
Para quem ainda está se perguntando sobre a resposta à pergunta de Leon, use a classe base comum ( Runnable) e, se necessário, faça uma verificação de tipo mais específica no argumento capturado.
Matthew Leia
50

Desde o Mockito 2.0, também existe a possibilidade de usar o método estático Matchers.argThat (ArgumentMatcher) . Com a ajuda do Java 8, agora é muito mais limpo e mais legível escrever:

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

Se você está vinculado à versão Java inferior, também não é tão ruim assim:

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

Obviamente, nenhum deles pode verificar a ordem das chamadas - para as quais você deve usar o 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")));

Por favor, dê uma olhada no projeto mockito-java8 que torna possível fazer chamadas como:

verify(mockBar).doSth(assertArg(arg -> assertThat(arg.getSurname()).isEqualTo("Surname")));
Maciej Dobrowolski
fonte
2
Essa é uma boa técnica. No momento, estou obtendo uma saída bastante enigmática: "Desejado, mas não invocado: / n mockAppender.append (<Gerenciador de índices ut $$ lambda $ 5 9/1 3 1 9 5 1 0 1 6>);" - o argumento existe um CharSequence. Você conhece alguma maneira de obter o relatório para imprimir corretamente o argumento "desejado"?
microfone roedor
@mikerodent A saída enigmática pode ser corrigida se você seguir a rota mais detalhada da criação de uma classe que implemente ArgumentMatcher <T>. A substituição do método toString em sua implementação fornecerá qualquer mensagem desejada na saída do teste de mockito.
Noah Solomon
25

Se você não deseja validar todas as chamadas doSomething(), somente a última, basta usar ArgumentCaptor.getValue(). De acordo com o Mockito javadoc :

Se o método foi chamado várias vezes, ele retornará o último valor capturado

Então, isso funcionaria (assume que Footem um método 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
fonte
existe alguma maneira de capturar os dois valores?
Hars
9

Você também pode usar o ArgumentCaptor anotado no @Captor. Por exemplo:

@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));
}
Michał Stochmal
fonte
6

Com os lambdas do Java 8, uma maneira conveniente é usar

org.mockito.invocation.InvocationOnMock

when(client.deleteByQuery(anyString(), anyString())).then(invocationOnMock -> {
    assertEquals("myCollection", invocationOnMock.getArgument(0));
    assertThat(invocationOnMock.getArgument(1), Matchers.startsWith("id:"));
}
Anton Seredkin
fonte
Não consigo ver como isso é mais conveniente do que o jeito antigo. Adoro o bom uso de lambdas, mas não tenho certeza se é esse.
11138 Eric Wilson #
0

Primeiro de tudo: você deve sempre importar mockito estático, assim o código será muito mais legível (e intuitivo) - os exemplos de código abaixo exigem que ele funcione:

import static org.mockito.Mockito.*;

No método confirm (), você pode passar o ArgumentCaptor para garantir a execução no teste e o ArgumentCaptor para avaliar os argumentos:

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());
}

A lista de todos os argumentos passados ​​durante o seu teste pode ser acessada pelo método argument.getAllValues ​​().

O valor do argumento único (última chamada) pode ser acessado via argument.getValue () para manipulação / verificação adicional ou o que você deseja fazer.

fl0w
fonte