Використовуйте Mockito для знущання над деякими методами, але не з інших


402

Чи є якийсь спосіб, використовуючи 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);
}

4
Чому б ти хотів це зробити? Вам слід або тестувати клас (у такому випадку з нього не повинно бути глузувань), або слід знущатися над ним під час тестування іншого класу (у такому випадку немає функціональності). Чому б ти зробив частковий глум?
weltraumpirat

3
Гаразд, це невеликий приклад реальної речі. Насправді я намагаюся уникати виклику до бази даних, передаючи надумані значення, але хочу переконатися, що інші методи працюють правильно з цими надуманими значеннями. Чи є кращий спосіб зробити це?
Віктор Гразі

5
Звичайно: перемістіть виклики вашої бази даних до окремого класу (логіка домену та доступ до бази даних не повинні бути в одному класі; вони є двома різними проблемами), витягніть його інтерфейс, використовуйте цей інтерфейс для підключення з класу логіки домену та знущайтеся з лише інтерфейс під час тестування.
weltraumpirat

1
Я повністю згоден, важко пояснити всю картину, не завантажуючи сюди коди, включаючи сторонні бібліотеки.
Віктор Граці

1
Ви, мабуть, могли. Але тоді це не буде "кращим способом зробити це": Ваш код бази даних - це детальна інформація про реалізацію, яку ви хочете сховати від решти програми, ймовірно, навіть перейти до іншого пакету. Ви не хочете, щоб перекомпілювати свою доменну логіку щоразу, коли ви змінюєте наступний вислів, чи не так?
weltraumpirat

Відповіді:


643

Щоб безпосередньо відповісти на ваше запитання, так, ви можете знущатися над одними методами, не знущаючись над іншими. Це називається частковим знущанням . Для отримання додаткової інформації дивіться документацію 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(..)пункті.

Також є можливість навпаки зі шпигуном замість макету :

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)із шпигуном, як у попередньому прикладі. Буде викликано справжній метод (тому що 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(), що то , що ви глузували.

Інша можливість - взагалі уникнути знущань:

@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);
}

21
Я думаю, що ця відповідь неправильна. Потрібно шпігувати екземпляр об'єкта, а не MOCK класу.
GaRRaPeTa

2
@GaRRaPeTa Я б сказав, що шпигунство та глузування - це обидві альтернативи. Важко сказати, що найкраще для даного випадку, оскільки ОП заявляє, що це спрощений приклад.
Джон Ньюмуас

1
Чи не може це бути "Шпигун", а не "Макет", оскільки частковий глузливий кабін "Шпигун" буде наданий кращим чином.
Тарун Сапра

2
Stock stock = spy(Stock.class);Це здається неправильним, spyметод, здається, приймає лише об'єкти, а не класи.
Paramvir Singh Karwal

4
+1 для зазначення різниці між doReturn(retval).when(spyObj).methodName(args)іwhen(spyObj.methodName(args)).thenReturn(retval)
Captain_Obvious

140

Часткове глузування з класу також підтримується через 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документи.


37

За документами :

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();

2
Дякуємо, що продемонстрували, як налаштувати макет, коли реальна реалізація викликається для всіх методів, за винятком кількох, які мені потрібно контролювати з тесту.
bigh_29

class NaughtyLinkedList extends LinkedList { public int size() { throw new RuntimeException("don't call me");} } @Test public void partialMockNaughtLinkedList(){ List mock = mock(NaughtyLinkedList.class, CALLS_REAL_METHODS); mock.add(new Object()); // this calls the real function when(mock.size()).thenReturn(2); // For whatever reason, this lines throws the RuntimeException. assertEquals(2,mock.size()); }Це не працює. З будь-якої причини, коли "коли" виконується, він фактично виконує метод, з якого слід знущатися. Код:
Lance Kind

3
Проблема - "коли". "Коли" насправді виконає те, що потрібно частково знущатися. Щоб уникнути цього, існує альтернатива: doReturn (). Дивіться doReturn () на docs.mockito.googlecode.com/hg/1.9.5/org/mockito/…
Lance Kind

18

Те, що ви хочете, - 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);дзвінки 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.)
 */

1
Добре помічений ... але чи можу я просто запитати, чому ви так використовуєте withSettings()...? Здається, що org.mockito.internal.stubbing.answers.CallsRealMethods()(наприклад) може зробити цю роботу ... і javadoc для цього класу спеціально говорить, що він використовується для часткових глузувань ...
Майк гризун

3
Крім того ... це не стане проблемою, з якою тут стикаються інші відповіді: тобто thenReturnреально виконати метод (що може спричинити проблеми, хоча не в цьому прикладі), і тому doReturnбажано в такому випадку ...?
мійський гризун

4

Часткове глузування за допомогою шпигунського методу Mockito може бути вирішенням вашої проблеми, як уже було зазначено у відповідях вище. Я певною мірою погоджуюся з тим, що для вашого конкретного випадку використання може бути більш доречним глузування з пошуку БД. З мого досвіду, це не завжди можливо - принаймні, без інших обхідних шляхів - які я вважаю б дуже громіздкими або принаймні крихкими. Зауважте, що частковий глузування не працює із союзними версіями Mockito. Ви використовуєте принаймні 1.8.0.

Я б тільки написав простий коментар до оригінального питання, а не публікувати цю відповідь, але StackOverflow цього не дозволяє.

Ще одне: я дійсно не можу зрозуміти, що багато разів тут задають питання, коментуючи "Чому ти хочеш це зробити", не намагаючись зрозуміти проблему. Особливо, якщо мова йде про необхідність часткового глузування, то справді багато випадків використання, які я міг би уявити, де це було б корисно. Ось чому хлопці з Mockito забезпечили цю функціональність. Цією особливістю, звичайно, не слід користуватися. Але коли ми говоримо про установки тестових справ, які в іншому випадку не могли бути встановлені дуже складним способом, слід використовувати шпигунство.


2
Я вважаю, що ця відповідь частково є думкою. Будь ласка, розгляньте редагування
soundlikeodd

2
Прихильний підбадьорити нового члена сім’ї. Не потрібно отримувати цю зону in -ve, там нічого насправді технічно невірно чи неправильна мова / тон. Будьте ласкаві з новими членами. Дякую.
Саурабх Патіль
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.