Use Mockito para zombar de alguns methods, mas não de outros

Existe alguma maneira, usando Mockito, para zombar de alguns methods em uma class, mas não em outros?

Por exemplo, nesta class Stock (admitidamente planejada), eu quero zombar dos valores de retorno getPrice () e getQuantity () (como mostrado no snippet de teste abaixo), mas quero que o getValue () execute a multiplicação como codificada no Stock class

public class Stock { private final double price; private final int quantity; Stock(double price, int quantity) { this.price = price; this.quantity = quantity; } public double getPrice() { return price; } public int getQuantity() { return quantity; } public double getValue() { return getPrice() * getQuantity(); } @Test public void getValueTest() { Stock stock = mock(Stock.class); when(stock.getPrice()).thenReturn(100.00); when(stock.getQuantity()).thenReturn(200); double value = stock.getValue(); // Unfortunately the following assert fails, because the mock Stock getValue() method does not perform the Stock.getValue() calculation code. assertEquals("Stock value not correct", 100.00*200, value, .00001); } 

Para responder diretamente a sua pergunta, sim, você pode zombar de alguns methods sem zombar dos outros. Isso é chamado de simulação parcial . Veja a documentação do Mockito em simulações parciais para mais informações.

Por exemplo, você pode fazer algo parecido com o seguinte, em seu teste:

 Stock stock = mock(Stock.class); when(stock.getPrice()).thenReturn(100.00); // Mock implementation when(stock.getQuantity()).thenReturn(200); // Mock implementation when(stock.getValue()).thenCallRealMethod(); // Real implementation 

Nesse caso, cada implementação de método é thenCallRealMethod() , a menos que seja especificado thenCallRealMethod() na cláusula when(..) .

Existe também a possibilidade de o outro lado ser espionado em vez de simulado :

 Stock stock = spy(Stock.class); when(stock.getPrice()).thenReturn(100.00); // Mock implementation when(stock.getQuantity()).thenReturn(200); // Mock implementation // All other method call will use the real implementations 

Nesse caso, toda a implementação do método é a real, exceto se você tiver definido um comportamento com zombaria when(..) .

Há uma armadilha importante quando você usa when(Object) com spy como no exemplo anterior. O método real será chamado (porque stock.getPrice() é avaliado antes when(..) em tempo de execução). Isso pode ser um problema se seu método contiver lógica que não deve ser chamada. Você pode escrever o exemplo anterior assim:

 Stock stock = spy(Stock.class); doReturn(100.00).when(stock).getPrice(); // Mock implementation doReturn(200).when(stock).getQuantity(); // Mock implementation // All other method call will use the real implementations 

No entanto, com seu exemplo, acredito que ele ainda falhará, pois a implementação de getValue() depende da quantity e do price , em vez de getQuantity() e getPrice() , que é o que você escarneceu.

O que realmente parece que você quer é apenas:

 @Test public void getValueTest() { Stock stock = new Stock(100.00, 200); double value = stock.getValue(); assertEquals("Stock value not correct", 100.00*200, value, .00001); } 

Zombaria parcial de uma class também é suportado via Spy in mockito

 List list = new LinkedList(); List spy = spy(list); //optionally, you can stub out some methods: when(spy.size()).thenReturn(100); //using the spy calls real methods spy.add("one"); spy.add("two"); //size() method was stubbed - 100 is printed System.out.println(spy.size()); 

Consulte os documentos 2.7.22 e 2.7.22 para obter explicações detalhadas.

Segundo docs :

 Foo mock = mock(Foo.class, CALLS_REAL_METHODS); // this calls the real implementation of Foo.getSomething() value = mock.getSomething(); when(mock.getSomething()).thenReturn(fakeValue); // now fakeValue is returned value = mock.getSomething(); 

A resposta aceita não está correta de acordo com a pergunta.

A chamada para Stock stock = mock(Stock.class); chama org.mockito.Mockito.mock(Class) que se parece com isso:

  public static  T mock(Class classToMock) { return mock(classToMock, withSettings().defaultAnswer(RETURNS_DEFAULTS)); } 

Os documentos do valor RETURNS_DEFAULTS dizem:

 /** * The default Answer of every mock if the mock was not stubbed. * Typically it just returns some empty value. * 

* {@link Answer} can be used to define the return values of unstubbed invocations. *

* This implementation first tries the global configuration. * If there is no global configuration then it uses {@link ReturnsEmptyValues} (returns zeros, empty collections, nulls, etc.) */

O que você quer é org.mockito.Mockito.CALLS_REAL_METHODS acordo com os documentos:

 /** * Optional Answer to be used with {@link Mockito#mock(Class, Answer)} * 

* {@link Answer} can be used to define the return values of unstubbed invocations. *

* This implementation can be helpful when working with legacy code. * When this implementation is used, unstubbed methods will delegate to the real implementation. * This is a way to create a partial mock object that calls real methods by default. *

* As usual you are going to read the partial mock warning: * Object oriented programming is more less tackling complexity by dividing the complexity into separate, specific, SRPy objects. * How does partial mock fit into this paradigm? Well, it just doesn't... * Partial mock usually means that the complexity has been moved to a different method on the same object. * In most cases, this is not the way you want to design your application. *

* However, there are rare cases when partial mocks come handy: * dealing with code you cannot change easily (3rd party interfaces, interim refactoring of legacy code etc.) * However, I wouldn't use partial mocks for new, test-driven & well-designed code. *

* Example: *

 * Foo mock = mock(Foo.class, CALLS_REAL_METHODS); * * // this calls the real implementation of Foo.getSomething() * value = mock.getSomething(); * * when(mock.getSomething()).thenReturn(fakeValue); * * // now fakeValue is returned * value = mock.getSomething(); * 

*/

Assim, seu código deve se parecer com:

 import org.junit.Test; import static org.mockito.Mockito.*; import static org.junit.Assert.*; public class StockTest { public class Stock { private final double price; private final int quantity; Stock(double price, int quantity) { this.price = price; this.quantity = quantity; } public double getPrice() { return price; } public int getQuantity() { return quantity; } public double getValue() { return getPrice() * getQuantity(); } } @Test public void getValueTest() { Stock stock = mock(Stock.class, withSettings().defaultAnswer(CALLS_REAL_METHODS)); when(stock.getPrice()).thenReturn(100.00); when(stock.getQuantity()).thenReturn(200); double value = stock.getValue(); assertEquals("Stock value not correct", 100.00 * 200, value, .00001); } } 

A zombaria parcial usando o método de espionagem de Mockito pode ser a solução para o seu problema, como já foi dito nas respostas acima. Até certo ponto, concordo que, para o seu caso de uso concreto, pode ser mais apropriado ridicularizar a pesquisa do database. Pela minha experiência, isso nem sempre é possível – pelo menos não sem outras soluções alternativas – que eu consideraria muito complicado ou, pelo menos, frágil. Note que a zombaria parcial não funciona com versões aliadas do Mockito. Você usa pelo menos 1.8.0.

Eu teria apenas escrito um comentário simples para a pergunta original em vez de postar essa resposta, mas o StackOverflow não permite isso.

Só mais uma coisa: eu realmente não consigo entender que muitas vezes uma pergunta está sendo feita aqui recebe comentário com “Por que você quer fazer isso” sem pelo menos tentar entender o problema. Especialmente quando se trata de necessidade de zombaria parcial, há muitos casos de uso que eu poderia imaginar onde seria útil. É por isso que os caras do Mockito forneceram essa funcionalidade. Esse recurso certamente não deve ser usado em excesso. Mas quando falamos sobre configurações de casos de teste que de outra forma não poderiam ser estabelecidas de uma maneira muito complicada, a espionagem deveria ser usada.