«1. Обзор

Термин BDD был введен Дэном Нортом в 2006 году.

BDD поощряет написание тестов на естественном, понятном человеку языке, который фокусируется на поведении приложения.

Он определяет четко структурированный способ написания тестов, следующих трем разделам (Arrange, Act, Assert):

    заданы некоторые предварительные условия (Arrange), когда происходит действие (Act), затем проверка вывода (Assert)

Библиотека Mockito поставляется с классом BDDMockito, который представляет API-интерфейсы, удобные для BDD. Этот API позволяет нам использовать более дружественный к BDD подход, организуя наши тесты с помощью данного() и делая утверждения с помощью then().

В этой статье мы объясним, как настроить наши тесты Mockito на основе BDD. Мы также поговорим о различиях между API Mockito и BDDMockito, чтобы в конечном итоге сосредоточиться на API BDDMockito.

2. Настройка

2.1. Зависимости Maven

Вариант BDD Mockito является частью библиотеки mockito-core, чтобы начать работу, нам просто нужно включить артефакт:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.21.0</version>
</dependency>

Для получения последней версии Mockito, пожалуйста, проверьте Maven Central.

2.2. Импорт

Наши тесты могут стать более читабельными, если мы включим следующий статический импорт:

import static org.mockito.BDDMockito.*;

Обратите внимание, что BDDMockito расширяет Mockito, поэтому мы не упустим ни одной функции, предоставляемой традиционным API Mockito.

3. Mockito против BDDMockito

Традиционный макет в Mockito выполняется с использованием when(obj).then*() на шаге аранжировки.

Позже взаимодействие с нашим макетом можно будет проверить с помощью функции verify() на этапе Assert.

BDDMockito предоставляет псевдонимы BDD для различных методов Mockito, поэтому мы можем написать наш шаг аранжировки, используя заданное (вместо когда), точно так же мы можем написать наш шаг утверждения, используя then (вместо проверки).

Давайте рассмотрим пример тестового тела с использованием традиционного Mockito:

when(phoneBookRepository.contains(momContactName))
  .thenReturn(false);
 
phoneBookService.register(momContactName, momPhoneNumber);
 
verify(phoneBookRepository)
  .insert(momContactName, momPhoneNumber);

Давайте посмотрим, как это сравнивается с BDDMockito:

given(phoneBookRepository.contains(momContactName))
  .willReturn(false);
 
phoneBookService.register(momContactName, momPhoneNumber);
 
then(phoneBookRepository)
  .should()
  .insert(momContactName, momPhoneNumber);

4. Мокинг с помощью BDDMockito

Давайте попробуем протестировать PhoneBookService где нам нужно смоделировать PhoneBookRepository:

public class PhoneBookService {
    private PhoneBookRepository phoneBookRepository;

    public void register(String name, String phone) {
        if(!name.isEmpty() && !phone.isEmpty()
          && !phoneBookRepository.contains(name)) {
            phoneBookRepository.insert(name, phone);
        }
    }

    public String search(String name) {
        if(!name.isEmpty() && phoneBookRepository.contains(name)) {
            return phoneBookRepository.getPhoneNumberByContactName(name);
        }
        return null;
    }
}

BDDMockito, поскольку Mockito позволяет нам возвращать значение, которое может быть фиксированным или динамическим. Это также позволило бы нам генерировать исключение:

4.1. Возврат фиксированного значения

Используя BDDMockito, мы можем легко настроить Mockito для возврата фиксированного результата всякий раз, когда вызывается наш целевой метод фиктивного объекта:

given(phoneBookRepository.contains(momContactName))
  .willReturn(false);
 
phoneBookService.register(xContactName, "");
 
then(phoneBookRepository)
  .should(never())
  .insert(momContactName, momPhoneNumber);

4.2. Возврат динамического значения

BDDMockito позволяет нам предоставить более сложный способ возврата значений. Мы могли бы вернуть динамический результат на основе ввода:

given(phoneBookRepository.contains(momContactName))
  .willReturn(true);
given(phoneBookRepository.getPhoneNumberByContactName(momContactName))
  .will((InvocationOnMock invocation) ->
    invocation.getArgument(0).equals(momContactName) 
      ? momPhoneNumber 
      : null);
phoneBookService.search(momContactName);
then(phoneBookRepository)
  .should()
  .getPhoneNumberByContactName(momContactName);

4.3. Генерация исключения

Сказать Mockito генерировать исключение довольно просто:

given(phoneBookRepository.contains(xContactName))
  .willReturn(false);
willThrow(new RuntimeException())
  .given(phoneBookRepository)
  .insert(any(String.class), eq(tooLongPhoneNumber));

try {
    phoneBookService.register(xContactName, tooLongPhoneNumber);
    fail("Should throw exception");
} catch (RuntimeException ex) { }

then(phoneBookRepository)
  .should(never())
  .insert(momContactName, tooLongPhoneNumber);

Обратите внимание, как мы поменяли местами заданное и will*, это обязательно, если мы имитируем метод, который не имеет возвращаемого значения.

Также обратите внимание, что мы использовали сопоставители аргументов, такие как (any, eq), чтобы обеспечить более общий способ имитации на основе критериев, а не в зависимости от фиксированного значения.

5. Заключение

В этом кратком руководстве мы обсудили, как BDDMockito пытается добиться сходства BDD с нашими тестами Mockito, а также обсудили некоторые различия между Mockito и BDDMockito.

Как всегда, исходный код можно найти на GitHub — в тестовом пакете com.baeldung.bddmockito.