Использование 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();
// К сожалению, следующий assert не проходит, так как замокированный метод getValue() не выполняет код вычисления из Stock.getValue().
assertEquals("Стоимость акций неверна", 100.00*200, value, .00001);
}
2 ответ(ов)
Вы можете использовать org.mockito.Mockito.CALLS_REAL_METHODS
, как указано в документации:
/**
* Опциональный <code>Answer</code>, который можно использовать с {@link Mockito#mock(Class, Answer)}
* <p>
* {@link Answer} может быть использован для определения значений, возвращаемых не заглушенными вызовами.
* <p>
* Эта реализация может быть полезна при работе с устаревшим кодом.
* Когда используется эта реализация, не заглушенные методы будут делегировать вызовы на реальную реализацию.
* Это способ создать частичную заглушку, которая по умолчанию вызывает реальные методы.
* <p>
* Как обычно, вы должны прочитать <b>предупреждение о частичной заглушке</b>:
* Объектно-ориентированное программирование в большей степени направлено на решение сложности путем деления на отдельные, специфические объекты, соблюдающие принцип единственной ответственности (SRP).
* Как частичная заглушка вписывается в эту парадигму? Фактически, она не вписывается...
* Частичная заглушка обычно означает, что сложность была перенесена в другой метод того же объекта.
* В большинстве случаев это не лучший способ проектирования вашего приложения.
* <p>
* Тем не менее, есть редкие случаи, когда частичные заглушки могут оказаться полезными:
* если у вас есть код, который нельзя легко изменить (интерфейсы сторонних разработчиков, промежуточная рефакторификация устаревшего кода и т. д.).
* Однако я бы не стал использовать частичные заглушки для нового, управляемого тестами и хорошо спроектированного кода.
* <p>
* Пример:
* <pre class="code"><code class="java">
* Foo mock = mock(Foo.class, CALLS_REAL_METHODS);
*
* // это вызывает реальную реализацию Foo.getSomething()
* value = mock.getSomething();
*
* when(mock.getSomething()).thenReturn(fakeValue);
*
* // теперь возвращается fakeValue
* 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("Значение акции неверно", 100.00 * 200, value, .00001);
}
}
Вызов Stock stock = mock(Stock.class);
вызывает org.mockito.Mockito.mock(Class<T>)
, который выглядит следующим образом:
public static <T> T mock(Class<T> classToMock) {
return mock(classToMock, withSettings().defaultAnswer(RETURNS_DEFAULTS));
}
Документация по значению RETURNS_DEFAULTS
гласит:
/**
* Значение по умолчанию <code>Answer</code> для каждой заглушки <b>если</b> заглушка не была заглушена.
* Обычно она просто возвращает какое-то пустое значение.
* <p>
* {@link Answer} может быть использован для определения значений, возвращаемых не заглушенными вызовами.
* <p>
* Эта реализация сначала пытается использовать глобальную конфигурацию.
* Если глобальной конфигурации нет, тогда используется {@link ReturnsEmptyValues} (возвращает нули, пустые коллекции, нули и т. д.)
*/
Частичное мокирование с использованием метода spy библиотеки Mockito может быть решением вашей проблемы, как уже упоминалось в предыдущих ответах. В какой-то степени я согласен с тем, что для вашего конкретного случая может быть более уместно замокировать обращение к базе данных. Однако, исходя из моего опыта, это не всегда возможно - по крайней мере, не без других обходных путей, которые я бы считал довольно громоздкими или хотя бы хрупкими. Обратите внимание, что частичное мокирование работает не со всеми версиями Mockito. Вам потребуется как минимум версия 1.8.0.
Я бы просто написал краткий комментарий к исходному вопросу, а не публиковал бы этот ответ, но StackOverflow этого не позволяет.
Еще один момент: мне совершенно непонятно, почему многие вопросы здесь сопровождаются комментариями в духе "Зачем вы хотите это сделать?", не пытаясь хотя бы понять суть проблемы. Особенно когда речь идет о необходимости частичного мокирования, я могу представить множество сценариев, где это было бы полезно. Именно поэтому разработчики Mockito реализовали эту функциональность. Конечно, эту возможность не следует использовать чрезмерно. Но когда мы говорим о настройках тестов, которые в противном случае было бы очень сложно установить, использование spy просто необходимо.
Как замокировать методы с возвращаемым типом void с помощью Mockito
Mockito: Тестирование void метода, который выбрасывает исключение
Можно ли с помощью Mockito захватывать аргументы метода, вызываемого несколько раз?
"Как сделать так, чтобы имитируемый метод возвращал аргумент, который был ему передан?"
Как проверить, что конкретный метод не был вызван, используя Mockito?