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.
Este comentário foi removido pelo autor.
ResponderExcluirParabéns Diego, muito bom o post.
ResponderExcluirNo caso do spy, não seria mais "puro" usar uma classe anônima?
Não me sinto muito confortável de testar um classe/subject com spy, uma classe com cglib.
Fiz alguns exemplos de refactory de código legado até uma implementação "ideal". O primeiro passo é com classe anônima.
https://gist.github.com/3108659
O que você acha?
Será que é só preconceito meu com o spy? hehehe
De verdade eu acho que você tá certo os spies devem ser usados com cuidado.
ExcluirSe você ver mesmo na documentação do Mockito sobre spies eles não recomendam muito o uso pois, de verdade usa-los indica que você possui um código altamente acoplado.
E isso é ruim, mesmo que você consiga usar um spy para testar :) o que ajuda em parte não muda o fato de você ter um alto acoplamento.
Eu achei boa sua solução :) e é preferível fazer um refactory e arrumar o código que manter do jeito atual.
E tem até um se você ver no wiki do Mockito, tem a seguinte página que te dá alternativas de como Mockar criação de objetos, http://code.google.com/p/mockito/wiki/MockingObjectCreation.
Valeu pelo comentário