Недавно вышел Spring Framework 6.2, который включил в себя множество улучшений и новых возможностей. К одним из них можно отнести переопределение бинов в тестах.

В новом переводе от команды Spring АйО вы узнаете как можно использовать @TestBean, @MockitoBean и @MockitoSpyBean для переопределения бинов в тестах.


Spring TestContext Framework позволяет легко проверить, как компоненты вашего Spring-приложения работают вместе в интеграционных тестах. Это достигается с помощью удобных аннотаций, которые упрощают настройку тестового окружения.

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

Команда Spring Framework в целом не рекомендует переопределять бины. Хотя в текущей реализации BeanDefinitionRegistry это возможно с помощью специального флага. Мы планируем отказаться от этой фичи, и в Spring Boot переопределение бинов уже отключено по умолчанию.

Однако это больше касается исходных кодов, а не тестов. Мы согласны, что в тестах переопределение бинов может быть полезным и оправданным. Поэтому мы решили предоставить полноценные инструменты для таких случаев.

В Spring Framework 6.2.0-M1 появилась функция переопределения бинов. Она позволяет точно и явно заменять один или несколько бинов в интеграционных тестах, при этом предотвращая нежелательные изменения в исходном коде или других частях тестов.

Простое переопределение бинов с помощью @TestBean

В Spring TestContext Framework появилась простая возможность переопределения бинов с использованием аннотации @TestBean.

Чтобы переопределить бин с именем example, нужно выполнить три шага: добавить поле с таким же именем, как у бина, отметить его аннотацией @TestBean и создать статический фабричный метод без аргументов с именем exampleTestOverride. В этом методе, например, можно вернуть упрощенную реализацию, если бин представляет собой интерфейс:

@Configuration
class ProdConfiguration {

  @Bean
  MyService customService() {
    return new ProdServiceImpl();
  }
}

@SpringJUnitConfig
class MyServiceIntegrationTests {

  @TestBean
  MyService customService;

  static MyService customServiceTestOverride() {
    return new SimplifiedServiceImpl();
  }

  @Test
  void test(ApplicationContext context) {
    assertThat(context.getBean("customService")
      .isSameAs(this.customService)
      .isInstanceOf(SimplifiedServiceImpl.class);
    //...
  }
}

Имя аннотированного поля интерпретируется как имя бина, если в аннотации @TestBean не указано другое имя через параметр beanName.

Параметр methodName также можно использовать для указания фабричного метода, который не соответствует стандартному соглашению об именах вида {beanName}TestOverride.

Механизм переопределения бинов отвечает за обработку этой аннотации и замену существующего определения бина в реестре. Поле customService в тестовом классе также будет автоматически заполнено экземпляром, возвращаемым фабричным методом customServiceTestOverride.

Переопределение на основе Mockito с использованием аннотаций @MockitoBean и @MockitoSpyBean

Этот способ переопределения бинов основан на библиотеке Mockito. Он представляет собой использование двух аннотаций: @MockitoBean, которая автоматически заменяет целевой бин на мок, и @MockitoSpyBean, которая оборачивает бин в spy.

Каждая из этих аннотаций имеет параметры, специфичные для Mockito, которые позволяют настраивать, как должен быть замокирован бин. Мы в том числе можем указать, как моки должны сбрасываться между тестами, как показано в следующем примере:

@Configuration
class ProdConfiguration {

  @Bean
  MyService customService() {
    return new ProdService();
  }
}

@SpringJUnitConfig
class MyServiceIntegrationTests {

  @MockitoSpyBean(reset = MockReset.NONE)
  MyService customService;

  @Test
  void test() {
    //...
  }
}

В приведённом примере spy не будет сбрасываться между тестами. По умолчанию моки и spy сбрасываются после выполнения метода теста.

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

Расширение с использованием собственной реализации  

Новое переопределение компонентов (Bean Overriding) в поддержке тестов реализовано в виде аннотационной модели, которая применяется к полям в тестовых классах. Эта модель расширяемая и настраиваемая, а три аннотации, описанные выше, являются лишь стандартными реализациями, доступными "из коробки".  

Создать собственный вариант переопределения компонентов так же просто, как:  

  1. Создать аннотацию, которая является мета-аннотацией для @BeanOverride и указывает, какой BeanOverrideProcessor использовать.  

  2. Реализовать сам BeanOverrideProcessor.

  3. Предоставить одну или несколько конкретных реализаций OverrideMetadata, которые используются процессором.

Spring TestContext Framework анализирует тестовые классы, ищет поля, помеченные мета-аннотацией @BeanOverride, и создает соответствующий экземпляр BeanOverrideProcessor, чтобы зарегистрировать экземпляр OverrideMetadata.  

Затем BeanFactoryPostProcessor использует эту информацию для изменения контекста: регистрации и замены определений компонентов в соответствии с метаданными.

Заключение

Spring TestContext Framework теперь предлагает два способа переопределения компонентов в тестах без риска получить непреднамеренные побочные эффекты. Механизм переопределения компонентов расширяем, что может быть полезно, например, если вы предпочитаете использовать библиотеку мокирования, отличную от Mockito.  

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

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.

Ждем всех, присоединяйтесь!

Комментарии (1)


  1. Djaler
    18.11.2024 18:10

    Вероятно, стоит упомянуть о том, что такие манипуляции приводят к необходимости заново поднимать контекст.