Mockito peut-il capturer les arguments d'une méthode appelée plusieurs fois?

446

J'ai une méthode qui est appelée deux fois et je veux capturer l'argument du deuxième appel de méthode.

Voici ce que j'ai essayé:

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

Mais je reçois une TooManyActualInvocationsexception, car Mockito pense que cela doSomethingne devrait être appelé qu'une seule fois.

Comment puis-je vérifier l'argument du deuxième appel de doSomething?

Eric Wilson
la source

Réponses:

784

Je pense que ça devrait être

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

Échantillon 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());
proactif
la source
3
Pouvez-vous capturer les arguments passés à doSomething()chaque appel distinct avec ceci?
mat
36
Il convient de noter que dans le cas où vous faites quelque chose comme ceci: Person person = new Person("John"); doSomething(person); person.setName("Jane"); doSomething(person);l'argument capturé sera le même deux fois (car en fait c'est le même objet personne), alors capturedPeople.get(0).getName() == capturedPeople.get(1).getName() == "Jane", voir aussi groups.google.com/forum/#!msg/mockito/ KBRocVedYT0 / 5HtARMl9r2wJ .
asmaier
2
C'est bien, mais comment puis-je tester deux invocations d'objets de types différents? Par exemple ExecutorService.submit (new MyRunableImpl ()); puis ExecutorService.submit (new MyAnotherRunableImpl ())?
Leon
Si l'on doit gérer le cas décrit par @asmaier, j'ai posté une réponse ici: stackoverflow.com/a/36574817/1466267
SpaceTrucker
1
Pour quiconque se pose encore des questions sur la réponse à la question de Léon, vous utiliserez la classe de base commune ( Runnable) et, si nécessaire, effectuerez une vérification de type plus spécifique sur l'argument capturé.
Matthieu lu
50

Depuis Mockito 2.0, il est également possible d'utiliser la méthode statique Matchers.argThat (ArgumentMatcher) . Avec l'aide de Java 8, il est maintenant beaucoup plus propre et plus lisible d'écrire:

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

Si vous êtes lié à une version Java inférieure, il n'y a pas non plus si mal:

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

Bien sûr, aucun de ceux-ci ne peut vérifier l'ordre des appels - pour lequel vous devez utiliser 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")));

Veuillez jeter un œil au projet mockito-java8 qui permet de faire des appels tels que:

verify(mockBar).doSth(assertArg(arg -> assertThat(arg.getSurname()).isEqualTo("Surname")));
Maciej Dobrowolski
la source
2
C'est une belle technique. Je reçois actuellement une sortie plutôt cryptée: "Wanted but not invoked: / n mockAppender.append (<Index manager ut $$ lambda $ 5 9/1 3 1 9 5 1 0 1 6>);" - l'argument il y a un CharSequence. Connaissez-vous un moyen d'obtenir le rapport pour imprimer correctement l'argument "recherché"?
mike rodent
@mikerodent La sortie cryptique peut être corrigée si vous optez pour la voie la plus verbeuse de création d'une classe qui implémente ArgumentMatcher <T>. Le remplacement de la méthode toString dans votre implémentation fournira tout message souhaité dans la sortie du test mockito.
Noah Solomon
25

Si vous ne souhaitez pas valider tous les appels doSomething(), uniquement le dernier, vous pouvez simplement utiliser ArgumentCaptor.getValue(). Selon le javadoc Mockito :

Si la méthode a été appelée plusieurs fois, elle renvoie la dernière valeur capturée

Donc, cela fonctionnerait (suppose Fooune méthode 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
la source
existe-t-il un moyen de capturer les deux valeurs?
Hars
9

Vous pouvez également utiliser ArgumentCaptor annoté @Captor. Par exemple:

@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
la source
6

Avec les lambdas de Java 8, un moyen pratique consiste à utiliser

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
la source
Je ne suis pas en mesure de voir comment cela est plus pratique que l'ancienne. J'aime la bonne utilisation des lambdas, mais je ne sais pas si c'est le cas.
Eric Wilson
0

Tout d'abord: vous devez toujours importer mockito static, de cette façon le code sera beaucoup plus lisible (et intuitif) - les exemples de code ci-dessous nécessitent qu'il fonctionne:

import static org.mockito.Mockito.*;

Dans la méthode verify (), vous pouvez passer ArgumentCaptor pour assurer l'exécution dans le test et ArgumentCaptor pour évaluer les arguments:

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

La liste de tous les arguments passés pendant votre test est accessible via la méthode argument.getAllValues ​​().

La valeur de l'argument unique (dernier appelé) est accessible via l'argument.getValue () pour une manipulation / vérification supplémentaire ou tout ce que vous souhaitez faire.

fl0w
la source