Pour vérifier le nombre d'interactions avec une maquette lorsque le paramètre dans l'appel de méthode est d'un certain type, on peut faire
mock.someMethod(new FirstClass());
mock.someMethod(new OtherClass());
verify(mock, times(1)).someMethod(isA(FirstClass.class));
Cela passera grâce à l'appel à isA
puisque someMethod
a été appelé deux fois mais seulement une fois avec l'argument FirstClass
Cependant, ce modèle semble ne pas être possible lorsqu’on utilise un ArgumentCaptor, même si le capteur a été créé pour l’argument particulier FirstClass
ça ne marche pas
mock.someMethod(new FirstClass());
mock.someMethod(new OtherClass());
ArgumentCaptor<FirstClass> captor = ArgumentCaptor.forClass(FirstClass.class);
verify(mock, times(1)).someMethod(captor.capture());
il dit que la maquette a été appelée plus d'une fois.
Y a-t-il un moyen d'accomplir cette vérification tout en capturant l'argument en vue d'une vérification supplémentaire?
Je recommande d'utiliser l'intégration Hamcrest de Mockito pour écrire un bon et propre matcher. Cela vous permet de combiner la vérification avec la vérification détaillée de l'argument passé:
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);
}
};
}
Les correspondants entraînent généralement des tests plus lisibles et des messages d'échec de test plus utiles. Ils ont également tendance à être très réutilisables et vous devrez en constituer une bibliothèque sur mesure pour tester votre projet. Enfin, vous pouvez également les utiliser pour des assertions de test normales à l'aide de la fonction Assert.assertThat()
de JUnit, de sorte que vous en obtiendrez une double utilisation.
Citant les docs:
Notez qu'une
ArgumentCaptor
ne fait aucune vérification de type , c'est seulement là pour éviter de jeter dans votre code. Cela pourrait toutefois changer (des contrôles de type Pourraient être ajoutés) dans une future version majeure.
Je ne voudrais pas utiliser un ArgumentCaptor
pour cela. Cette classe capture (littéralement) tout, malgré la classe fournie car c'est l'argument .forClass
.
Pour obtenir ce que vous voulez, je suggère d'intercepter l'argument à l'aide de l'interface Answer
de Mockito:
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));
}
Vous pouvez utiliser le capteur pour capturer, puis vérifier le nombre d'appels avec chaque type d'argument séparément.
// 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());
Apparemment, le type générique de la référence de capteur n'affecte en rien l'exécution.
J'ai aussi rencontré ce problème aujourd'hui. Je pensais que je pouvais simplement faire quelque chose comme
verify(mock).someMethod(and(isA(FirstClass.class), captor.capture()));
mais je ne pouvais pas le faire fonctionner. J'ai fini avec cette solution:
@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;
}
Remarque: si une seule classe doit être capturée de cette manière, typeCheckedValues
peut être simplifié en:
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;
}