Existe-t-il un moyen de capturer une liste de types spécifiques à l'aide de mockitos ArgumentCaptore. Cela ne fonctionne pas:
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);
Existe-t-il un moyen de capturer une liste de types spécifiques à l'aide de mockitos ArgumentCaptore. Cela ne fonctionne pas:
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);
Réponses:
Le problème des génériques imbriqués peut être évité avec l' annotation @Captor :
public class Test{
@Mock
private Service service;
@Captor
private ArgumentCaptor<ArrayList<SomeType>> captor;
@Before
public void init(){
MockitoAnnotations.initMocks(this);
}
@Test
public void shouldDoStuffWithListValues() {
//...
verify(service).doStuff(captor.capture()));
}
}
MockitoAnnotations.initMocks(this)
dans la @Before
méthode plutôt que d'utiliser un coureur qui exclut la possibilité d'utiliser un autre coureur. Cependant, +1, merci d'avoir souligné l'annotation.
Oui, c'est un problème générique général, pas spécifique aux mockito.
Il n'y a pas d'objet de classe pour ArrayList<SomeType>
, et donc vous ne pouvez pas passer un tel objet en toute sécurité à une méthode nécessitant un Class<ArrayList<SomeType>>
.
Vous pouvez convertir l'objet au bon type:
Class<ArrayList<SomeType>> listClass =
(Class<ArrayList<SomeType>>)(Class)ArrayList.class;
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(listClass);
Cela donnera des avertissements sur les conversions dangereuses, et bien sûr, votre ArgumentCaptor ne peut pas vraiment faire la différence entre ArrayList<SomeType>
et ArrayList<AnotherType>
sans peut-être inspecter les éléments.
(Comme mentionné dans l'autre réponse, bien qu'il s'agisse d'un problème générique général, il existe une solution spécifique à Mockito pour le problème de sécurité de type avec l' @Captor
annotation. Elle ne peut toujours pas distinguer entre un ArrayList<SomeType>
et un ArrayList<OtherType>
.)
Jetez également un œil au commentaire de tenshi . Vous pouvez changer le code original de Paŭlo Ebermann en ceci (beaucoup plus simple)
final ArgumentCaptor<List<SomeType>> listCaptor
= ArgumentCaptor.forClass((Class) List.class);
ArgumentCaptor<List<SimeType>> argument = ArgumentCaptor.forClass((Class) List.class);
@SuppressWarnings("unchecked")
annotation au-dessus de la ligne de définition du capteur d'arguments. En outre, la diffusion vers Class
est redondante.
Class
n'est pas redondant dans mes tests.
Si vous n'avez pas peur de l'ancienne sémantique de style java (générique non sécurisé de type), cela fonctionne également et est assez simple:
ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
verify(subject.method(argument.capture()); // run your code
List<SomeType> list = argument.getValue(); // first captured List, etc.
List<String> mockedList = mock(List.class);
List<String> l = new ArrayList();
l.add("someElement");
mockedList.addAll(l);
ArgumentCaptor<List> argumentCaptor = ArgumentCaptor.forClass(List.class);
verify(mockedList).addAll(argumentCaptor.capture());
List<String> capturedArgument = argumentCaptor.<List<String>>getValue();
assertThat(capturedArgument, hasItem("someElement"));
Sur la base des commentaires de @ tenshi et @ pkalinow (également bravo à @rogerdpack), ce qui suit est une solution simple pour créer un capteur d'argument de liste qui désactive également l' avertissement "utilise des opérations non contrôlées ou dangereuses" :
@SuppressWarnings("unchecked")
final ArgumentCaptor<List<SomeType>> someTypeListArgumentCaptor =
ArgumentCaptor.forClass(List.class);
Exemple complet ici et génération de CI et test de passage correspondants ici .
Notre équipe l'utilise depuis un certain temps dans nos tests unitaires et cela semble être la solution la plus simple pour nous.
Pour une version antérieure de junit, vous pouvez le faire
Class<Map<String, String>> mapClass = (Class) Map.class;
ArgumentCaptor<Map<String, String>> mapCaptor = ArgumentCaptor.forClass(mapClass);
J'ai eu le même problème avec l'activité de test dans mon application Android. J'ai utilisé ActivityInstrumentationTestCase2
et je MockitoAnnotations.initMocks(this);
n'ai pas travaillé. J'ai résolu ce problème avec une autre classe avec respectivement champ. Par exemple:
class CaptorHolder {
@Captor
ArgumentCaptor<Callback<AuthResponse>> captor;
public CaptorHolder() {
MockitoAnnotations.initMocks(this);
}
}
Ensuite, dans la méthode de test d'activité:
HubstaffService hubstaffService = mock(HubstaffService.class);
fragment.setHubstaffService(hubstaffService);
CaptorHolder captorHolder = new CaptorHolder();
ArgumentCaptor<Callback<AuthResponse>> captor = captorHolder.captor;
onView(withId(R.id.signInBtn))
.perform(click());
verify(hubstaffService).authorize(anyString(), anyString(), captor.capture());
Callback<AuthResponse> callback = captor.getValue();
Il y a un problème ouvert dans le GitHub de Mockito à propos de ce problème exact.
J'ai trouvé une solution de contournement simple qui ne vous oblige pas à utiliser des annotations dans vos tests:
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.MockitoAnnotations;
public final class MockitoCaptorExtensions {
public static <T> ArgumentCaptor<T> captorFor(final CaptorTypeReference<T> argumentTypeReference) {
return new CaptorContainer<T>().captor;
}
public static <T> ArgumentCaptor<T> captorFor(final Class<T> argumentClass) {
return ArgumentCaptor.forClass(argumentClass);
}
public interface CaptorTypeReference<T> {
static <T> CaptorTypeReference<T> genericType() {
return new CaptorTypeReference<T>() {
};
}
default T nullOfGenericType() {
return null;
}
}
private static final class CaptorContainer<T> {
@Captor
private ArgumentCaptor<T> captor;
private CaptorContainer() {
MockitoAnnotations.initMocks(this);
}
}
}
Ce qui se passe ici, c'est que nous créons une nouvelle classe avec l' @Captor
annotation et y injectons le capteur. Ensuite, nous extrayons simplement le capteur et le renvoyons de notre méthode statique.
Dans votre test, vous pouvez l'utiliser comme ceci:
ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(genericType());
Ou avec une syntaxe qui ressemble à celle de Jackson TypeReference
:
ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(
new CaptorTypeReference<Supplier<Set<List<Object>>>>() {
}
);
Cela fonctionne, car Mockito n'a en fait besoin d'aucune information de type (contrairement aux sérialiseurs, par exemple).
ArrayList
). Vous pouvez toujours utiliser l'List
interface, et si vous voulez représenter le fait que c'est covariant, alors vous pouvez utiliserextends
:ArgumentCaptor<? extends List<SomeType>>