Como verificar várias chamadas de método com parâmetros diferentes

116

Tenho o seguinte método no qual desejo verificar o comportamento.

public void methodToTest(Exception e, ActionErrors errors) {
    ...

    errors.add("exception.message", 
            ActionMessageFactory.createErrorMessage(e.toString()));

    errors.add("exception.detail",
            ActionMessageFactory.createErrorMessage(e.getStackTrace()[0].toString()));

    ...
}

Na minha classe @Test, eu esperava fazer algo assim para verificar se errors.add()é chamado com "exception.message" e novamente com "exception.detail"

verify(errors).add(eq("exception.message"), any(ActionError.class));
verify(errors).add(eq("exception.detail"), any(ActionError.class));

no entanto Mockito reclama da seguinte forma

Argument(s) are different! Wanted:
actionErrors.add(
    "exception.message",
    <any>
);

Actual invocation has different arguments:
actionErrors.add(
    "exception.detail",
    org.apache.struts.action.ActionError@38063806
);

Como posso dizer ao Mockito para verificar os dois valores?

Brad
fonte
1
quando você tem 2 métodos com assinaturas diferentes, você pode escrever casos de teste separados para ambos.
Naveen Babu
8
Sim, mas neste caso é a mesma assinatura de método, mas apenas valores de argumento diferentes
Brad
você poderia tentar usarMockito.reset()
takacsot de

Respostas:

102

Leituras adicionais me levaram a tentar usar ArgumentCaptors e os trabalhos a seguir, embora muito mais prolixos do que eu gostaria.

ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);

verify(errors, atLeastOnce()).add(argument.capture(), any(ActionMessage.class));

List<String> values = argument.getAllValues();

assertTrue(values.contains("exception.message"));
assertTrue(values.contains("exception.detail"));
Brad
fonte
existe uma maneira de ter certeza de que determinados parâmetros foram emparelhados usando essa abordagem? Digamos, por exemplo, que o método do OP tivesse dois argumentos e quisesse verificar se eles foram chamados juntos
cometido e
1
O caso de teste do OP chama methodToTest()exatamente uma vez, portanto, essa resposta verifica se as duas chamadas são feitas juntas. O capturado List<String> valuesque está sendo declarado conterá apenas os dois valores sendo testados e nenhum outro. Você também pode adicionar assertTrue(values.size == 2). Se é isso que você quer, eu substituiria as 3 declarações assertTrue por uma única Hamcrest ...assertThat(values, contains("exception.message", "exception.detail"));
Brad
o caso de teste do OP não chama methodToTest () duas vezes?
comprometeu-se em
desculpe, eu não fui claro. Eu estava me referindo ao cenário em que OP queria testar se dois argumentos eram chamados em conjunto. Portanto, a assinatura do método seria semelhante a public void methodToTest (Erros de exceção e, mensagem m, ActionErrors) {para que uma exceção específica seja chamada com uma mensagem específica. Eu supor que você poderia apenas ter dois ArgumentCaptors e depois recuperar o índice e comparar com os valores contidos nesses índices em ambas as listas de valores
committedandroider
O caso de teste do OP chama methodToTest()uma vez. É o argumento do método ActionErrors errorsé chamado internamente duas vezes.
Brad
61

Se a ordem de ambas as add()chamadas for relevante, você pode usar InOrder:

InOrder inOrder = inOrder(errors);
inOrder.verify(errors).add(eq("exception.message"), any(ActionError.class));
inOrder.verify(errors).add(eq("exception.detail"), any(ActionError.class));
Christoph Walesch
fonte
7
É o suficiente para passar um único errorsargumento: InOrder inOrder = inOrder(errors);(ver documentos )
GreenhouseVeg
2
E se o pedido NÃO for relevante? o que geralmente é o caso.
haelix
1
@haelix Nesse caso, use a resposta de Brad. Converta Listpara Sete afirme que o conjunto de entradas é igual ao conjunto fornecido pelas capturas de argumento.
flopshot de
25

Experimente algo assim:

verify(errors, times(2))
     .add(AdditionalMatchers.or(eq("exception.message"), eq("exception.detail")),
          any(ActionError.class));
John B
fonte
5
Seu cheque está obviamente muito relaxado.
haelix
17

você provavelmente tem um problema no seu código. Porque na verdade você escreve este código:

Map<Character, String> map = mock(Map.class);

map.put('a', "a");
map.put('b', "b");
map.put('c', "c");

verify(map).put(eq('c'), anyString());
verify(map).put(eq('a'), anyString());
verify(map).put(eq('b'), anyString());

Observe que a primeira verificação não está uniforme em relação às invocações reais.

Além disso, recomendo que você realmente não zombe de tipos que você não possui, por exemplo, o tipo struts.

[EDIT @Brad]

Depois de executar o código de Brice (acima) em meu IDE, posso ver que usei ActionError em vez de ActionMessage, então é por isso que meu verify () não estava combinando. A mensagem de erro que postei inicialmente estava me enganando, fazendo-me pensar que era o primeiro argumento incompatível. Acontece que foi o segundo argumento.

Então, a resposta à minha pergunta é

/** 
 * note that ActionMessageFactory.createErrorMessage() returns ActionMessage
 * and ActionError extends ActionMessage
 */
verify(errors).add(eq("exception.message"), any(ActionMessage.class));
verify(errors).add(eq("exception.detail"), any(ActionMessage.class));
Brice
fonte
1
Não entendo o que você está tentando dizer. A ordem da verificação é importante? se a ordem de verificação importa. Por que então aqui é fornecida a API InOrder?
Oleksandr Papchenko
Assim como o que está escrito acima do pedido de verificação é irrelevante; é por isso que existe InOrder.
Brice
12

Você pode usar o Mockito.atLeastOnce()qual permite que o Mockito passe no teste, mesmo se o mockObject for chamado muitas vezes.

Mockito.verify(mockObject, Mockito.atLeastOnce()).testMethod(Mockito.eq(1));

Mockito.verify(mockObject, Mockito.atLeastOnce()).testMethod(Mockito.eq(2));
sendon1982
fonte
1

1) Diga a Mokito a expectativa total de ligações.

2) Diga a Mokito quantas vezes cada combinação de parâmetro era esperada.

verify(errors, times(2)).add(any(), any(ActionMessage.class));

verify(errors, atLeastOnce()).add(eq("exception.message"), any());
verify(errors, atLeastOnce()).add(eq("exception.detail"), any());
epóxi
fonte
0

De forma semelhante a @ sendon1928, podemos usar:

Mockito.times(wantedInvocationCount)

para ter certeza de que o método foi chamado um número exato de vezes (solução preferível na minha opinião). Depois, podemos ligar

Mockito.verifyNoMoreInteractions(mock)

Para ter certeza de que a simulação não foi mais usada em nenhum contexto. Exemplo completo:

Mockito.verify(mockObject, Mockito.times(wantedInvocationCount)).testMethod(Mockito.eq(1));

Mockito.verify(mockObject, Mockito.times(wantedInvocationCount)).testMethod(Mockito.eq(2));

Mockito.verifyNoMoreInteractions(mockObject)
Michał Powłoka
fonte