Comment faire correspondre correctement les varargs dans Mockito

152

J'ai essayé de simuler une méthode avec des paramètres vararg en utilisant Mockito:

interface A {
  B b(int x, int y, C... c);
}

A a = mock(A.class);
B b = mock(B.class);

when(a.b(anyInt(), anyInt(), any(C[].class))).thenReturn(b);
assertEquals(b, a.b(1, 2));

Cela ne fonctionne pas, mais si je fais cela à la place:

when(a.b(anyInt(), anyInt())).thenReturn(b);
assertEquals(b, a.b(1, 2));

Cela fonctionne, même si j'ai complètement omis l'argument varargs lors du stub de la méthode.

Des indices?

qualificatif
la source
le fait que le dernier exemple fonctionne est assez trivial car il correspond au cas où zéro paramètre varargs est passé.
topchef

Réponses:

235

Mockito 1.8.1 a introduit le matcher anyVararg () :

when(a.b(anyInt(), anyInt(), Matchers.<String>anyVararg())).thenReturn(b);

Consultez également l'historique pour cela: https://code.google.com/archive/p/mockito/issues/62

Modifiez la nouvelle syntaxe après la désapprobation:

when(a.b(anyInt(), anyInt(), ArgumentMatchers.<String>any())).thenReturn(b);
excellent chef
la source
26
anyVararg()a Object comme type de retour. Pour le rendre compatible avec tous les types var arg (par exemple, String ..., Integer ..., etc.), effectuez un cast explicite. Par exemple, si vous avez, doSomething(Integer number, String ... args)vous pouvez faire le code simulé / stub avec quelque chose comme when(mock).doSomething(eq(1), (String) anyVarargs()). Cela devrait résoudre l'erreur de compilation.
Psycho Punch
15
pour info anyVararg est désormais obsolète: "@deprecated from 2.1.0 use any ()"
alexbt
5
Matchersest désormais obsolète afin d'éviter un conflit de nom avec la org.hamcrest.Matchersclasse et sera probablement supprimé dans mockito v3.0. Utilisez ArgumentMatchersplutôt.
JonyD
31

Une fonctionnalité quelque peu non documentée: si vous souhaitez développer un Matcher personnalisé qui correspond aux arguments vararg, vous devez l'implémenter org.mockito.internal.matchers.VarargMatcherpour qu'il fonctionne correctement. C'est une interface de marqueur vide, sans laquelle Mockito ne comparera pas correctement les arguments lors de l'appel d'une méthode avec varargs en utilisant votre Matcher.

Par exemple:

class MyVarargMatcher extends ArgumentMatcher<C[]> implements VarargMatcher {
    @Override public boolean matches(Object varargArgument) {
        return /* does it match? */ true;
    }
}

when(a.b(anyInt(), anyInt(), argThat(new MyVarargMatcher()))).thenReturn(b);
Eli Levine
la source
7

S'appuyant sur la réponse d'Eli Levine, voici une solution plus générique:

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.mockito.ArgumentMatcher;
import org.mockito.internal.matchers.VarargMatcher;

import static org.mockito.Matchers.argThat;

public class VarArgMatcher<T> extends ArgumentMatcher<T[]> implements VarargMatcher {

    public static <T> T[] varArgThat(Matcher<T[]> hamcrestMatcher) {
        argThat(new VarArgMatcher(hamcrestMatcher));
        return null;
    }

    private final Matcher<T[]> hamcrestMatcher;

    private VarArgMatcher(Matcher<T[]> hamcrestMatcher) {
        this.hamcrestMatcher = hamcrestMatcher;
    }

    @Override
    public boolean matches(Object o) {
        return hamcrestMatcher.matches(o);
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("has varargs: ").appendDescriptionOf(hamcrestMatcher);
    }

}

Ensuite, vous pouvez l'utiliser avec les matchers de tableau de hamcrest ainsi:

verify(a).b(VarArgMatcher.varArgThat(
            org.hamcrest.collection.IsArrayContaining.hasItemInArray("Test")));

(Évidemment, les importations statiques rendront cela plus lisible.)

Peter Westmacott
la source
Agréable. Cela devrait être intégré à Mockito IMO.
bryant
J'ai déposé un problème contre Hamcrest pour ajouter quelque chose comme ça. Voir github.com/mockito/mockito/issues/356
Mark le
Est-ce pour Mockito 1? J'obtiens diverses erreurs de compilation en essayant de compiler avec 2.10.
Frans
@Frans, il semble que la version 2 était encore en version bêta lorsque j'ai écrit cette réponse, donc oui, elle a probablement été écrite pour Mockito v1.10.19 ou à peu près. ( github.com/mockito/mockito/releases ) Il est probablement modifiable ... :-D
Peter Westmacott
3

J'ai utilisé le code dans la réponse de Peter Westmacott, mais avec Mockito 2.2.15, vous pouvez maintenant faire ce qui suit:

verify(a).method(100L, arg1, arg2, arg3)

arg1, arg2, arg3sont varargs.

marque
la source
1

S'appuyant sur la réponse de topchef,

Pour la version 2.0.31-beta, j'ai dû utiliser Mockito.anyVararg au lieu de Matchers.anyVararrg:

when(a.b(anyInt(), anyInt(), Mockito.<String>anyVararg())).thenReturn(b);
NPike
la source
3
pour info anyVararg est désormais obsolète: "@deprecated from 2.1.0 use any ()"
alexbt
0

Dans mon cas, la signature de la méthode dont je veux capturer son argument est:

    public byte[] write(byte ... data) throws IOException;

Dans ce cas, vous devez convertir explicitement en tableau d'octets :

       when(spi.write((byte[])anyVararg())).thenReturn(someValue);

J'utilise la version mockito 1.10.19

Seyed Jalal Hosseini
la source
0

Vous pouvez également faire une boucle sur les arguments:

Object[] args = invocation.getArguments(); 
for( int argNo = 0; argNo < args.length; ++argNo) { 
    // ... do something with args[argNo] 
}

par exemple, vérifiez leurs types et lancez-les correctement, ajoutez-les à une liste ou autre.

Richard Whitehead
la source
0

Adaptation de la réponse de @topchef,

Mockito.when(a.b(Mockito.anyInt(), Mockito.anyInt(), Mockito.any())).thenReturn(b);

Selon la documentation java pour Mockito 2.23.4, Mockito.any () "Correspond à tout, y compris les valeurs nulles et les varargs."

Craig
la source