Ao contrário de Mocks com os Spies é possível chamar os métodos reais da classe.
List<String> listaSpy = new ArrayList<String>(); listaSpy = Mockito.spy(listaSpy); listaSpy.add("item1"); //Chama o método verdadeiro do objeto e retorna a String = "item1" listaSpy.get(0);
Mockito.doReturn("outra coisa").when(listaSpy).get(0); //Não chama o método verdadeiro e retorna a String = "outra coisa" listaSpy.get(0);
Ao contrário dos Mocks um objeto Spy age como se fosse um Semi mock, em outras palavras, quando não definimos um outro comportamento para o Spy, seu funcionamento é igual ao de um objeto normal. No entanto ao definirmos um outro comportamento (linha 8) ele se comporta como um Mock para o caso que definimos.
Ok e eu com isso?! Vamos ver um uso mais real de um spy.
O uso que eu tive ao usar o Spy foi para Mockar criação/instanciação de objetos.
Por exemplo, na classe Connection temos o seguinte método.
//declaração da classe Connection public boolean connect() { try { socket = new Socket(hostAddress, port); printStream = new PrintStream(socket.getOutputStream()); bufferedReader = new BufferedReader( new InputStreamReader(socket.getInputStream())); return true; } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return false; } public String readMessage() { //... return bufferedReader.readLine(); //... }
Como faremos para testar esta classe e o método readLine(), se no cenário atual nós não podemos criar um mock de BufferedReader em nosso teste e passa-la para o objeto Connect, pois o BufferedReader é instanciado dentro da classe Connect!. :/
Spy ao seu resgate! Uma solução é usar Spies para simular isso... então nosso primeiro passo é criar métodos na classe Connect que criem os objetos que precisamos.
Ficando assim o código.
//declaração da classe Connection public boolean connect() { //.. socket = createSocket(); printStream = createPrintStream(); bufferedReader = createInputStream(); return true; //... } public String readMessage() { //... return bufferedReader.readLine(); //... } protected Socket createSocket() //... protected PrintStream createPrintStream() //... protected BufferedReader createInputStream() //...
Veja que os métodos que criam os objetos são protected, isso é necessário pois o Mockito não consegue interceptar métodos que são privados.
O teste da classe ficará assim:
import static org.mockito.Mockito.*; import static org.junit.Asserts.*; @Test public void shouldReadMessage() throws UnknownHostException, IOException { //cria instancia da classe a ser testada Connection connection = new Connection("127.0.0.1", 14000); //transforma em um spy connection = spy(connection); //cria os mocks que serão retornados //pelos metodos de criação de objetos Socket socket = mock(Socket.class); BufferedReader bufferedReader = mock(BufferedReader.class); PrintStream printStream = mock(PrintStream.class); //define o comportamento dos métodos doReturn(socket).when(connection).createSocket(); doReturn(bufferedReader).when(connection).createInputStream(); doReturn(printStream).when(connection).createPrintStream(); when(bufferedReader.readLine()).thenReturn("readed from mock"); connection.connect(); assertNotNull(connection.readMessage()); }
Veja que apesar de termos que alterar a nossa classe e extrair as criações de objetos para métodos protected, foi possível criar um teste do método readMessage usando mocks dos objetos do socket.
O uso de Spies da maneira que foi mostrada, pode indicar que seu código esteja com um acoplamento alto e como sempre cabe pensar um pouco se não existe um jeito melhor de programar sua classe para evitar isto.
Isso conclui nossa série de posts sobre o Mockito, pelo menos por enquanto, espero muito que a pequena introdução que escrevi aqui os ajudem a criar seus testes unitários assim como tem me ajudado bastante.
Segue os links que usei de referência
Mocking object creation
Mockito class javadoc
Site do Mockito
Código fonte: https://github.com/diegoy/talks-mockito
Qualquer dúvida, como sempre perguntem, farei o possível para ajudar.