Como zombar de métodos vazios com o Mockito

938

Como zombar de métodos com o tipo de retorno nulo?

Eu implementei um padrão de observador, mas não posso zombar dele com Mockito porque não sei como.

E tentei encontrar um exemplo na Internet, mas não obtive sucesso.

Minha classe fica assim:

public class World {

    List<Listener> listeners;

    void addListener(Listener item) {
        listeners.add(item);
    }

    void doAction(Action goal,Object obj) {
        setState("i received");
        goal.doAction(obj);
        setState("i finished");
    }

    private string state;
    //setter getter state
} 

public class WorldTest implements Listener {

    @Test public void word{
    World  w= mock(World.class);
    w.addListener(this);
    ...
    ...

    }
}

interface Listener {
    void doAction();
}

O sistema não é acionado com simulação.

Eu quero mostrar o estado do sistema acima mencionado. E faça afirmações de acordo com eles.

ibrahimyilmaz
fonte
6
Cuidado que os métodos nulos nas zombarias não fazem nada por padrão!
Linha
1
@ Line, era isso que eu estava procurando. Parece óbvio depois que você diz. Mas destaca um princípio de zombaria: você só precisa zombar de métodos de classes zombadas para seus efeitos, como um valor de retorno ou uma exceção. Obrigado!
allenjom

Respostas:

1145

Dê uma olhada nos documentos da API do Mockito . Como o documento vinculado menciona (ponto # 12), você pode usar qualquer um dos doThrow(), doAnswer(), doNothing(), doReturn()família de métodos de quadro Mockito para zombar métodos vazios.

Por exemplo,

Mockito.doThrow(new Exception()).when(instance).methodName();

ou se você deseja combiná-lo com o comportamento de acompanhamento,

Mockito.doThrow(new Exception()).doNothing().when(instance).methodName();

Presumindo que você esteja zombando do levantador setState(String s)na classe Mundo abaixo, o código usa o doAnswermétodo para zombar do setState.

World  mockWorld = mock(World.class); 
doAnswer(new Answer<Void>() {
    public Void answer(InvocationOnMock invocation) {
      Object[] args = invocation.getArguments();
      System.out.println("called with arguments: " + Arrays.toString(args));
      return null;
    }
}).when(mockWorld).setState(anyString());
Sateesh
fonte
8
@qualidafial: Sim, acho que a parametrização para o Void seria melhor, pois indica melhor que não estou interessado no tipo de retorno. Eu não estava ciente dessa construção, obrigado por apontar.
Sateesh
2
doThrow é # 5 agora (também para mim, usando doThrow isso corrigiu a mensagem "o tipo 'void' não é permitido aqui", para os seguidores ...)
rogerdpack
@qualidafial: Eu acho que o tipo de retorno da chamada Answer.answer não é o que é retornado ao método original, é o que é retornado à chamada doAnswer, presumivelmente se você deseja fazer outra coisa com esse valor em seu teste.
twelve17
1
:( na tentativa de zombar da versão 16.0.1 do RateLimiter.java na goia doNothing (). when (mockLimiterReject) .setRate (100) resulta na chamada do setRate do RateLimiter, resultando em ponteiro nulo, pois o mutex é nulo por algum motivo, uma vez que o mockex é nulo por codificado lo para que ele não zombar meu método setRate :( mas em vez chamou-:(
Dean Hiller
2
O aviso @DeanHiller setRate()é esse finale, portanto, não pode ser ridicularizado. Em vez disso, tente create()criar uma instância que faça o que você precisa. Não deve haver necessidade de zombaria RateLimiter.
precisa saber é o seguinte
113

Acho que encontrei uma resposta mais simples para essa pergunta, para chamar o método real para apenas um método (mesmo que tenha um retorno nulo), você pode fazer isso:

Mockito.doCallRealMethod().when(<objectInstance>).<method>();
<objectInstance>.<method>();

Ou, você pode chamar o método real para todos os métodos dessa classe, fazendo o seguinte:

<Object> <objectInstance> = mock(<Object>.class, Mockito.CALLS_REAL_METHODS);
MarcioB
fonte
13
Esta é a resposta real aqui. O método spy () funciona bem, mas geralmente é reservado para quando você deseja que o objeto faça quase tudo normalmente.
Biggusjimmus
1
O que isto significa? Você está realmente chamando os métodos? Eu realmente nunca usei mockito antes.
obesechicken13
Sim, o mock chamará os métodos reais. Se você usar o @Mock, poderá especificar o mesmo com: @Mock (answer = Answers.CALLS_REAL_METHODS) para obter os mesmos resultados.
Ale
70

Adicionando o que o @sateesh disse, quando você apenas deseja zombar de um método nulo para impedir que o teste o chame, você pode usar o Spyseguinte:

World world = new World();
World spy = Mockito.spy(world);
Mockito.doNothing().when(spy).methodToMock();

Quando você deseja executar seu teste, certifique-se de chamar o método test no spyobjeto e não no worldobjeto. Por exemplo:

assertEquals(0,spy.methodToTestThatShouldReturnZero());
Omri374
fonte
57

A solução do chamado problema é usar um spy Mockito.spy (...) em vez de um mock Mockito.mock (..) .

O espião nos permite zombar parcial. Mockito é bom nesse assunto. Como você tem uma classe que não está completa, você zomba de algum lugar necessário nessa classe.

ibrahimyilmaz
fonte
3
Tropecei aqui porque tinha um problema semelhante (também, coincidentemente, estava testando uma interação Assunto / Observador). Já estou usando um espião, mas quero que o método 'SubjectChanged' faça algo diferente. Eu poderia usar `verifique (observador) .subjectChanged (assunto) apenas para ver que o método foi chamado. Mas, por algum motivo, prefiro substituir o método. Para isso, uma combinação de abordagem de Sateesh e aqui a sua resposta foi o caminho a percorrer ...
gMale
32
Não, fazer isso não ajudará na zombaria de métodos nulos. O truque é usar um dos quatro métodos estáticos do Mockito listados na resposta de Sateesh.
Dawood ibn Kareem
2
@ Gurnard para sua pergunta, dê uma olhada neste stackoverflow.com/questions/1087339/… .
Ibrahimyilmaz
Este dosent realmente funciona.
Nilotpal
34

Primeiro de tudo: você deve sempre importar mockito estático, assim o código será muito mais legível (e intuitivo):

import static org.mockito.Mockito.*;

Para zombar parcial e ainda manter a funcionalidade original, o mockito oferece "Spy".

Você pode usá-lo da seguinte maneira:

private World world = spy(World.class);

Para evitar que um método seja executado, você pode usar algo como isto:

doNothing().when(someObject).someMethod(anyObject());

para dar um comportamento personalizado a um método, use "when" com um "thenReturn":

doReturn("something").when(this.world).someMethod(anyObject());

Para mais exemplos, encontre os excelentes exemplos de mockito no documento.

fl0w
fonte
4
O que a importação estática tem a ver com torná-la mais legível?
jsonbourne
2
literalmente nada.
specializt
2
Eu acho que é uma questão de gosto, eu só gostaria que a declaração parecesse (quase) uma frase em inglês e Class.methodname (). Algo () vs.
Fl0w 07/09/19
1
Se você trabalha em equipe, a importação estática torna difícil para qualquer pessoa ver de onde vem o método, pois geralmente há um curinga na importação.
LowKeyEnergy
isso está correto, no entanto, para o código de teste e o Mockito, isso deve ser óbvio e não um problema.
fl0w
27

Como zombar de métodos nulos com o mockito - existem duas opções:

  1. doAnswer - Se queremos que o nosso método de simulação faça algo (zombe do comportamento, apesar de ser nulo).
  2. doThrow- Então existe Mockito.doThrow()se você deseja lançar uma exceção do método de simulação falsa.

A seguir, é apresentado um exemplo de como usá-lo (não é um caso ideal, mas apenas para ilustrar o uso básico).

@Test
public void testUpdate() {

    doAnswer(new Answer<Void>() {

        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {
            Object[] arguments = invocation.getArguments();
            if (arguments != null && arguments.length > 1 && arguments[0] != null && arguments[1] != null) {

                Customer customer = (Customer) arguments[0];
                String email = (String) arguments[1];
                customer.setEmail(email);

            }
            return null;
        }
    }).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("[email protected]", "[email protected]");

    //some asserts
    assertThat(customer, is(notNullValue()));
    assertThat(customer.getEmail(), is(equalTo("[email protected]")));

}

@Test(expected = RuntimeException.class)
public void testUpdate_throwsException() {

    doThrow(RuntimeException.class).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("[email protected]", "[email protected]");

}
}

Você pode encontrar mais detalhes sobre como zombar e testar métodos nulos com o Mockito no meu post Como zombar do Mockito (Um guia completo com exemplos)

Dilini Rajapaksha
fonte
1
Ótimo exemplo. Nota: No java 8, pode ser um pouco melhor usar um lambda em vez de uma classe anônima: 'doAnswer ((Answer <Void>) invocation -> {// CODE}). When (mockInstance) .add (method ()); '
MIWE
16

Adicionando outra resposta ao grupo (sem trocadilhos) ...

Você precisa chamar o método doAnswer se não puder \ não quiser usar o espião. No entanto, você não precisa necessariamente lançar sua própria resposta . Existem várias implementações padrão. Notavelmente, CallsRealMethods .

Na prática, parece algo como isto:

doAnswer(new CallsRealMethods()).when(mock)
        .voidMethod(any(SomeParamClass.class));

Ou:

doAnswer(Answers.CALLS_REAL_METHODS.get()).when(mock)
        .voidMethod(any(SomeParamClass.class));
javamonkey79
fonte
16

No Java 8, isso pode ser um pouco mais limpo, supondo que você tenha uma importação estática para org.mockito.Mockito.doAnswer:

doAnswer(i -> {
  // Do stuff with i.getArguments() here
  return null;
}).when(*mock*).*method*(*methodArguments*);

O return null;é importante e, sem ele, a compilação falhará com alguns erros bastante obscuros, pois não será capaz de encontrar uma substituição adequada doAnswer.

Por exemplo, um ExecutorServiceque execute imediatamente qualquer Runnablepassagem para execute()poderia ser implementado usando:

doAnswer(i -> {
  ((Runnable) i.getArguments()[0]).run();
  return null;
}).when(executor).execute(any());
Tim B
fonte
2
Em uma linha: Mockito.doAnswer ((i) -> null) .when (instance) .method (any ());
Akshay Thorve 21/02/19
@AkshayThorve Isso não funciona quando você realmente quer fazer coisas com o i.
Tim B
7

Eu acho que seus problemas se devem à sua estrutura de teste. Achei difícil misturar a zombaria com o método tradicional de implementação de interfaces na classe de teste (como você fez aqui).

Se você implementar o ouvinte como um Mock, poderá verificar a interação.

Listener listener = mock(Listener.class);
w.addListener(listener);
world.doAction(..);
verify(listener).doAction();

Isso deve te convencer de que o 'Mundo' está fazendo a coisa certa.

ashley
fonte
0

Usando Mockito.doThrow como em:

Mockito.doThrow(new Exception()).when(instance).methodName();

você pode tentar este bom exemplo:

public void testCloseStreamOnException() throws Exception {
    OutputStream outputStream = Mockito.mock(OutputStream.class);
    IFileOutputStream ifos = new IFileOutputStream(outputStream);
    Mockito.doThrow(new IOException("Dummy Exception")).when(outputStream).flush();
    try {
      ifos.close();
      fail("IOException is not thrown");
    } catch (IOException ioe) {
      assertEquals("Dummy Exception", ioe.getMessage());
    }
    Mockito.verify(outputStream).close();
  }

Fonte: http://apisonar.com/java-examples/org.mockito.Mockito.doThrow.html#Example-19

APISonar
fonte