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?

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

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")));

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

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

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:"));
}

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.