Используйте Mockito для моделирования некоторых методов, но не других
Есть ли какой-либо способ, используя Mockito, имитировать некоторые методы в классе, но не другие?
Например, в этом (по общему признанию, надуманном) Stock
классе я хочу имитировать getPrice()
и getQuantity()
возвращаемые значения (как показано в приведенном ниже фрагменте теста), но я хочу, чтобы getValue()
выполнял умножение, как закодировано в Stock
классе
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);
}
Переведено автоматически
Ответ 1
Чтобы прямо ответить на ваш вопрос, да, вы можете имитировать некоторые методы, не имитируя другие. Это называется частичным макетом. Смотрите документацию Mockito по частичным макетам для получения дополнительной информации.
Для вашего примера вы можете сделать что-то вроде следующего в своем тесте:
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
В этом случае имитируется реализация каждого метода, если не указано thenCallRealMethod()
в when(..)
предложении.
Также существует возможность обратного использования spy вместо mock:
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
В этом случае вся реализация метода является реальной, за исключением случаев, когда вы определили имитируемое поведение с помощью when(..)
.
При использовании when(Object)
со spy, как в предыдущем примере, есть одна важная ошибка. Будет вызван реальный метод (потому что stock.getPrice()
оценивается перед when(..)
во время выполнения). Это может быть проблемой, если ваш метод содержит логику, которую не следует вызывать. Вы можете записать предыдущий пример следующим образом:
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
Другой возможностью может быть использование org.mockito.Mockito.CALLS_REAL_METHODS
, например:
Stock MOCK_STOCK = Mockito.mock( Stock.class, CALLS_REAL_METHODS );
Это делегирует неподтвержденные вызовы реальным реализациям.
Однако в вашем примере, я полагаю, это все равно приведет к сбою, поскольку реализация getValue()
опирается на quantity
и price
, а не на getQuantity()
и getPrice()
, над чем вы насмехались.
Другая возможность - вообще избегать mocks:
@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);
}
Ответ 2
Частичное моделирование класса также поддерживается через Spy в 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());
Проверьте 1.10.19
и 2.7.22
документы для подробного объяснения.
Ответ 3
Согласно документам :
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();
Ответ 4
То, что вы хотите, org.mockito.Mockito.CALLS_REAL_METHODS
согласно документам:
/**
* Optional <code>Answer</code> to be used with {@link Mockito#mock(Class, Answer)}
* <p>
* {@link Answer} can be used to define the return values of unstubbed invocations.
* <p>
* 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.
* <p>
* As usual you are going to read <b>the partial mock warning</b>:
* 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.
* <p>
* 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.
* <p>
* Example:
* <pre class="code"><code class="java">
* 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();
* </code></pre>
*/
Таким образом, ваш код должен выглядеть следующим образом:
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);
}
}
Вызов Stock stock = mock(Stock.class);
calls org.mockito.Mockito.mock(Class<T>)
, который выглядит следующим образом:
public static <T> T mock(Class<T> classToMock) {
return mock(classToMock, withSettings().defaultAnswer(RETURNS_DEFAULTS));
}
В документах о значении RETURNS_DEFAULTS
говорится:
/**
* The default <code>Answer</code> of every mock <b>if</b> the mock was not stubbed.
* Typically it just returns some empty value.
* <p>
* {@link Answer} can be used to define the return values of unstubbed invocations.
* <p>
* This implementation first tries the global configuration.
* If there is no global configuration then it uses {@link ReturnsEmptyValues} (returns zeros, empty collections, nulls, etc.)
*/