Статьи

Что такое дополнительные интерфейсы Mockito?

Мокито — мой любимый маленький помощник, если он собирается написать легкие тесты JUnit . При необходимости очень легко заменить «реальные» зависимости тестируемого модуля на макеты. В частности, при работе с интерфейсом API от границы до фреймворка такие зависимости в противном случае могут быть очень дорогими в настройке.

Но иногда ситуация немного сложнее. Например, если тест должен по какой-то причине взаимодействовать хотя бы с одним реальным экземпляром, который принадлежит такой платформе. Если это взаимодействие включает в себя передачу макета в качестве параметра этому экземпляру, то при удачной реализации реализация преобразует параметр в тип, который неизвестен с точки зрения интерактора.

Вот простой пример, чтобы прояснить это:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public interface Foo {
  [...]
}
 
public class Bar {
 
  public Bar( Foo foo ) {
    Runnable runnable = ( Runnable )foo;
    runnable.run();
  }
 
  [...]
}
 
public class SomeTest {
 
  @Test
  public void testConstructor() {
    Foo fooMock = mock( Foo.class );
 
    // fails with ClassCastException
    Bar bar = new Bar( fooMock );
 
    [...]
  }
}

Думайте о классе Bar как о фреймворковом коде, который ожидает определенный вид реализации. Поскольку тип параметра Foo не отражает это ожидание, передача макета Foo в конструктор Bar приведет к сбою теста с ClassCastException .

Возможно, первая мысль, которая приходит вам в голову, когда вы смотрите на ситуацию выше, состоит в том, что фреймворк отстой, приводя к необъявленному типу, и что лучше, выбрасывать все и начинать все заново!

К сожалению, существуют ситуации реального мира, где такое поведение, возможно, допустимо. Например, платформа Eclipse имеет множество интерфейсов, которые объявлены как «не предназначенные для реализации клиентами». Хорошим примером для этого является интерфейс IHistoryView командного API. Работая с платформой 3.x, можно быть уверенным, что реализация IViewPart расширяет IViewPart , хотя это деталь, которая не раскрывается интерфейсом представления истории.

Учитывая эти обстоятельства, иногда может быть желательно создать макет нескольких типов — макет, который реализует IViewPart и IViewPart — хотя API не указывает все из них. Mockito облегчает это с помощью менее известной возможности конфигурации MockSettings#extraInterfaces . В следующем фрагменте показано, как использовать extraInterfaces для исправления теста в приведенном выше примере.

01
02
03
04
05
06
07
08
09
10
@Test
public void testConstructor() {
  Foo mock = mock( Foo.class,
                   withSettings().extraInterfaces( Runnable.class ) );
 
  // the mock now supports the cast to runnable
  Bar bar = new Bar( mock );
 
  [...]
}

Вызов метода withSettings создает новый экземпляр MockSettings который настраивается с дополнительным типом Runnable . Сгенерированный макет Foo реализует Foo и Runnable . И сейчас тест проходит.

Однако имейте в виду, что хотя мотивация для использования дополнительных интерфейсов в этом посте может показаться правдоподобной, нельзя не подчеркнуть, что вам стоит подумать дважды, прежде чем использовать эту функцию. Или, как гласит документация: «Если вам случается использовать его часто, убедитесь, что вы действительно производите простой, чистый и читаемый код». И небрежно использованный это определенно предопределенный переломный момент.

Ссылка: Что такое дополнительные интерфейсы Mockito? от нашего партнера JCG Фрэнка Аппеля в блоге Code Affine .