Como combinar corretamente varargs no Mockito

152

Eu tenho tentado zombar de um método com parâmetros vararg usando o 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));

Isso não funciona, no entanto, se eu fizer isso:

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

Isso funciona, apesar de eu ter omitido completamente o argumento varargs ao remover o método.

Alguma pista?

qualidafial
fonte
o fato de que o último exemplo funciona é bastante trivial, pois corresponde ao caso em que zero parâmetros de varargs passaram.
Topchef

Respostas:

235

O Mockito 1.8.1 introduziu o matcher Vararg () :

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

Veja também o histórico para isso: https://code.google.com/archive/p/mockito/issues/62

Edite a nova sintaxe após a descontinuação:

when(a.b(anyInt(), anyInt(), ArgumentMatchers.<String>any())).thenReturn(b);
topchef
fonte
26
anyVararg()tem Object como seu tipo de retorno. Para torná-lo compatível com qualquer tipo de var arg (por exemplo, String ..., Inteiro ..., etc.), faça uma conversão explícita. Por exemplo, se você tiver, doSomething(Integer number, String ... args)poderá executar o código de simulação / stub com algo parecido when(mock).doSomething(eq(1), (String) anyVarargs()). Isso deve resolver o erro de compilação.
Psycho Punch
15
para informações anyVararg agora está obsoleta: "@ obsoleto a partir do 2.1.0 use any ()"
alexbt 13/11/16
5
Matchersagora está obsoleto para evitar um conflito de nome com a org.hamcrest.Matchersclasse e provavelmente será removido no mockito v3.0. Use em ArgumentMatchersvez disso.
21417 JJDD em
31

Um recurso não documentado: se você deseja desenvolver um Matcher personalizado que corresponda aos argumentos vararg, é necessário implementá org.mockito.internal.matchers.VarargMatcher-lo para que funcione corretamente. É uma interface de marcador vazia, sem a qual o Mockito não comparará corretamente os argumentos ao invocar um método com varargs usando seu Matcher.

Por exemplo:

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

Com base na resposta de Eli Levine, aqui está uma solução mais 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);
    }

}

Em seguida, você pode usá-lo com os correspondentes de matriz do hamcrest assim:

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

(Obviamente, as importações estáticas tornarão isso mais legível.)

Peter Westmacott
fonte
Agradável. Isso deve ser incorporado ao Mockito IMO.
Bryant
Eu registrei uma questão contra Hamcrest para adicionar algo assim. Veja github.com/mockito/mockito/issues/356
Marque
Isso é para Mockito 1? Eu recebo vários erros de compilação ao tentar compilar na versão 2.10.
Frans
@Frans parece que a versão 2 ainda estava em beta quando escrevi esta resposta, então sim, provavelmente foi escrita para o Mockito v1.10.19 ou por aí. ( Github.com/mockito/mockito/releases ) é provavelmente atualizável ... :-D
Peter Westmacott
3

Eu tenho usado o código na resposta de Peter Westmacott, no entanto, com o Mockito 2.2.15, agora você pode fazer o seguinte:

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

Onde arg1, arg2, arg3estão os varargs.

Marca
fonte
1

Com base na resposta do topchef,

Para o 2.0.31-beta, eu tive que usar o Mockito.anyVararg em vez do Matchers.anyVararrg:

when(a.b(anyInt(), anyInt(), Mockito.<String>anyVararg())).thenReturn(b);
NPike
fonte
3
para informações anyVararg agora está obsoleta: "@ obsoleto a partir do 2.1.0 use any ()"
alexbt 13/11/16
0

No meu caso, a assinatura do método que eu quero capturar seu argumento é:

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

Nesse caso, você deve converter a matriz de bytes explicitamente:

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

Estou usando a versão mockito 1.10.19

Seyed Jalal Hosseini
fonte
0

Você também pode fazer um loop sobre os argumentos:

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

por exemplo, verifique seus tipos e faça a conversão adequada, adicione a uma lista ou o que for.

Richard Whitehead
fonte
0

Adaptando a resposta de @topchef,

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

De acordo com os documentos java do Mockito 2.23.4, Mockito.any () "Corresponde a qualquer coisa, incluindo valores nulos e variáveis".

Craig
fonte