6

Использование Mockito для мокирования некоторых методов, но не всех

1

Существует ли способ, используя 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 ответ(ов)

0

Вы можете использовать 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} (возвращает нули, пустые коллекции, нули и т. д.)
 */
0

Частичное мокирование с использованием метода spy библиотеки Mockito может быть решением вашей проблемы, как уже упоминалось в предыдущих ответах. В какой-то степени я согласен с тем, что для вашего конкретного случая может быть более уместно замокировать обращение к базе данных. Однако, исходя из моего опыта, это не всегда возможно - по крайней мере, не без других обходных путей, которые я бы считал довольно громоздкими или хотя бы хрупкими. Обратите внимание, что частичное мокирование работает не со всеми версиями Mockito. Вам потребуется как минимум версия 1.8.0.

Я бы просто написал краткий комментарий к исходному вопросу, а не публиковал бы этот ответ, но StackOverflow этого не позволяет.

Еще один момент: мне совершенно непонятно, почему многие вопросы здесь сопровождаются комментариями в духе "Зачем вы хотите это сделать?", не пытаясь хотя бы понять суть проблемы. Особенно когда речь идет о необходимости частичного мокирования, я могу представить множество сценариев, где это было бы полезно. Именно поэтому разработчики Mockito реализовали эту функциональность. Конечно, эту возможность не следует использовать чрезмерно. Но когда мы говорим о настройках тестов, которые в противном случае было бы очень сложно установить, использование spy просто необходимо.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь