Cómo hacer coincidir correctamente los varargs en Mockito

152

He estado tratando de burlarme de un método con parámetros vararg usando 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));

Esto no funciona, sin embargo, si hago esto en su lugar:

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

Esto funciona, a pesar de que he omitido por completo el argumento varargs al tropezar con el método.

¿Alguna pista?

qualidafial
fuente
El hecho de que el último ejemplo funcione es bastante trivial, ya que coincide con el caso en que pasaron los parámetros de cero varargs.
topchef

Respuestas:

235

Mockito 1.8.1 introdujo anyVararg () matcher :

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

Consulte también el historial de esto: https://code.google.com/archive/p/mockito/issues/62

Edite una nueva sintaxis después de la depreciación:

when(a.b(anyInt(), anyInt(), ArgumentMatchers.<String>any())).thenReturn(b);
el mejor chef
fuente
26
anyVararg()tiene Object como su tipo de retorno. Para que sea compatible con cualquier tipo de var arg (por ejemplo, String ..., Integer ..., etc.), realice una conversión explícita. Por ejemplo, si tiene doSomething(Integer number, String ... args)puede hacer el código simulado / apéndice con algo como when(mock).doSomething(eq(1), (String) anyVarargs()). Eso debería encargarse del error de compilación.
Psycho Punch
15
para obtener información, anyVararg ahora está en desuso: "@deprecated a partir de 2.1.0 use any ()"
alexbt
55
Matchersahora está en desuso para evitar un choque de nombres con la org.hamcrest.Matchersclase y probablemente se eliminará en mockito v3.0. Usar en su ArgumentMatcherslugar.
JonyD
31

Una característica algo indocumentada: si desea desarrollar un Matcher personalizado que coincida con los argumentos vararg, debe implementarlo org.mockito.internal.matchers.VarargMatcherpara que funcione correctamente. Es una interfaz de marcador vacía, sin la cual Mockito no comparará correctamente los argumentos al invocar un método con varargs usando su Matcher.

Por ejemplo:

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
fuente
7

Aprovechar la respuesta de Eli Levine aquí es una solución más genérica:

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

}

Entonces puede usarlo con los emparejadores de matriz de hamcrest así:

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

(Obviamente, las importaciones estáticas harán que esto sea más legible).

Peter Westmacott
fuente
Agradable. Esto debería integrarse en Mockito IMO.
bryant
Presenté un problema contra Hamcrest para agregar algo como esto. Ver github.com/mockito/mockito/issues/356
Mark
¿Es esto para Mockito 1? Recibo varios errores de compilación cuando intento compilar contra 2.10.
Frans
@Frans parece que la versión 2 todavía estaba en beta cuando escribí esta respuesta, así que sí, probablemente fue escrita para Mockito v1.10.19 o por ahí. ( github.com/mockito/mockito/releases ) Probablemente sea actualizable ... :-D
Peter Westmacott
3

He estado usando el código en la respuesta de Peter Westmacott, sin embargo, con Mockito 2.2.15 ahora puede hacer lo siguiente:

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

¿Dónde arg1, arg2, arg3están los varargs?

marca
fuente
1

Sobre la base de la respuesta de topchef,

Para 2.0.31-beta tuve que usar Mockito.anyVararg en lugar de Matchers.anyVararrg:

when(a.b(anyInt(), anyInt(), Mockito.<String>anyVararg())).thenReturn(b);
NPike
fuente
3
para obtener información, anyVararg ahora está en desuso: "@deprecated a partir de 2.1.0 use any ()"
alexbt
0

En mi caso, la firma del método que quiero capturar es su argumento:

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

En este caso, debe convertir explícitamente en la matriz de bytes

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

Estoy usando la versión mockito 1.10.19

Seyed Jalal Hosseini
fuente
0

También puede recorrer los argumentos:

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

por ejemplo, verifique sus tipos y echéelos adecuadamente, agréguelos a una lista o lo que sea.

Richard Whitehead
fuente
0

Adaptando la respuesta de @topchef,

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

Según los documentos de Java para Mockito 2.23.4, Mockito.any () "Coincide con cualquier cosa, incluidos nulos y varargs".

Craig
fuente