Статьи

Удивительная инъекция

Итак, я должен извиниться перед Джимом. Он написал рабочий тест на mockito и JUnit, и я сказал ему в обзоре, что не думаю, что он сделал то, что ожидал. Хотя я был неправ, этот сценарий выглядит для меня как ошибка . Назовите это желательными неожиданными побочными эффектами.

Представьте, что у вас есть следующие два класса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class Service {
    private String name;
    private Widget widget;
 
    public Service(String name, Widget widget) {
        this.name = name;
        this.widget = widget;
    }
 
    public void execute() {
        widget.handle(name);
    }
}
 
public interface Widget {
    void handle(String thing);
}

Ничего захватывающего там нет …

Теперь давайте попробуем протестировать сервис с помощью теста Mockito (JUnit 5 здесь):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@ExtendWith(MockitoExtension.class)
class ServiceTest {
    @Mock
    private Widget widget;
 
    @InjectMocks
    private Service service = new Service("Joe", widget);
 
    @Test
    void foo() {
        service.execute();
        verify(widget).handle("Joe");
    }
}

Тест проходит. Но должно ли это?

Для чего InjectMocks ?

Для меня аннотация @InjectMocks предназначена для того, чтобы быть фабричным методом для создания чего-то, что зависит от фиктивных значений, выраженных с помощью @Mock в тесте. Вот как я обычно это использую, и я также ожидаю, что все объекты в моей экосистеме создаются с использованием инжектора конструктора.

Это хороший принцип проектирования, но это не определение того, что делает инструмент!

Что делает InjectMocks

Процесс применения этой аннотации просматривает поле, аннотированное @InjectMocks и @InjectMocks другой путь, если он null чем если он уже инициализирован. Будучи настолько чистым в том, что null путь является декларативным подходом к инжектированию конструктора, я совершенно не думал, что вставлять макеты может означать сделать это для существующего объекта. Документация также не совсем подтверждает это.

  • Если объекта нет, то @InjectMocks должен создать его
    • Он использует самый большой конструктор, который он может предоставить
  • Если есть объект, он пытается заполнить макеты через сеттеры
  • Если нет сеттеров, он пытается взломать макеты, непосредственно устанавливая поля, заставляя их быть доступными по пути

В довершение всего, @InjectMocks молча терпит неудачу, так что вы можете иметь неудачные тесты, не зная об этом.

В довершение всего, некоторые люди, использующие MockitoAnnotations.initMocks() в своих тестах, помимо Mockito Runner, вызывают всевозможные странности !!! Серьезно, ребята, никогда не называйте это.

Уроки выучены

Э-э … прости Джим!

Аннотация @InjectMocks пытается сделать все возможное, но чем сложнее сценарий, тем сложнее его предсказать.

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

Возможно, должен существовать какой-то вид @InjectWithFactory котором вы можете объявить метод, который получает @Mock и который @Mock при создании с объектами @Mock , чтобы вы могли заполнить любые другие параметры из остального контекста теста.

Или, может быть, мы просто привыкли к этой работе и забываем о том, легко ли это понять.

Последняя мысль

Я узнал, что Mockito делает в вышеописанном, создав тест и отладив библиотеку Mockito, чтобы выяснить, как она достигает результата. Я настоятельно рекомендую исследовать ваши наиболее часто используемые библиотеки таким образом. Вы узнаете что-то полезное!

Смотрите оригинальную статью здесь: Удивительная инъекция

Мнения, высказанные участниками Java Code Geeks, являются их собственными.