Русский
Русский
English
Статистика
Реклама

Перевод Запись событий Spring при тестировании приложений Spring Boot

Одна из основных функций Spring - функция публикации событий.Мы можем использовать события для разделения частей нашего приложения и реализации шаблона публикации-подписки.Одна часть нашего приложения может публиковать событие, на которое реагируют несколько слушателей (даже асинхронно).В рамкахSpring Framework 5.3.3(Spring Boot 2.4.2) теперь мы можем записывать и проверять все опубликованные события (ApplicationEvent) при тестировании приложений Spring Boot с использованием@RecrodApplicationEvents.

Настройка для записи ApplicationEvent с помощью Spring Boot

Чтобы использовать эту функцию, нам нужен толькоSpring Boot Starter Test,который является частью каждого проекта Spring Boot, который вы загружаете наstart.spring.io.

<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-test</artifactId>  <scope>test</scope></dependency>

Обязательно используйте версию Spring Boot >= 2.4.2, так как нам нужна версия Spring Framework >= 5.3.3.

Для наших тестов есть одно дополнительное требование: нам нужно работать со SpringTestContextпоскольку публикация событий является основной функциональностью платформыApplicationContext.

Следовательно, она не работает для модульного теста, где неиспользуется поддержка инфраструктуры Spring TestContext.Есть несколькоаннотаций тестовых срезов Spring Boot,которые удобно загружают контекст для нашего теста.

Введение в публикацию событий Spring

В качестве примера мы протестируем класс Java, который выдает UserCreationEvent,когда мы успешно создаем нового пользователя.Событие включает метаданные о пользователе, актуальные для последующих задач:

public class UserCreationEvent extends ApplicationEvent {   private final String username;  private final Long id;   public UserCreationEvent(Object source, String username, Long id) {    super(source);    this.username = username;    this.id = id;  }   // getters}

Начиная со Spring Framework 4.2, нам не нужно расширять абстрактныйкласс ApplicationEventи мы можем использовать любой POJO в качестве нашего класса событий.В следующий статье привеленоотличное введение в события приложенийс помощью Spring Boot.

НашUserServiceсоздает и хранит наших новых пользователей.Мы можем создать как одного пользователя, так и группу пользователей:

@Servicepublic class UserService {   private final ApplicationEventPublisher eventPublisher;   public UserService(ApplicationEventPublisher eventPublisher) {    this.eventPublisher = eventPublisher;  }   public Long createUser(String username) {    // logic to create a user and store it in a database    Long primaryKey = ThreadLocalRandom.current().nextLong(1, 1000);     this.eventPublisher.publishEvent(new UserCreationEvent(this, username, primaryKey));     return primaryKey;  }   public List<Long> createUser(List<String> usernames) {    List<Long> resultIds = new ArrayList<>();     for (String username : usernames) {      resultIds.add(createUser(username));    }     return resultIds;  }}

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

Например, наше приложение выполняет две дополнительные операции всякий раз, когда мы запускаем такоеUserCreationEvent:

@Componentpublic class ReportingListener {   @EventListener(UserCreationEvent.class)  public void reportUserCreation(UserCreationEvent event) {    // e.g. increment a counter to report the total amount of new users    System.out.println("Increment counter as new user was created: " + event);  }   @EventListener(UserCreationEvent.class)  public void syncUserToExternalSystem(UserCreationEvent event) {    // e.g. send a message to a messaging queue to inform other systems    System.out.println("informing other systems about new user: " + event);  }}

Запись и проверка событий приложения с помощью Spring Boot

Давайте напишем наш первый тест, который проверяет,UserServiceгенерирует событие всякий раз, когда мы создаем нового пользователя.Мы инструктируем Spring фиксировать наши события с помощью@RecordApplicationEventsаннотации поверх нашего тестового класса:

@SpringBootTest@RecordApplicationEventsclass UserServiceFullContextTest {   @Autowired  private ApplicationEvents applicationEvents;   @Autowired  private UserService userService;   @Test  void userCreationShouldPublishEvent() {     this.userService.createUser("duke");     assertEquals(1, applicationEvents      .stream(UserCreationEvent.class)      .filter(event -> event.getUsername().equals("duke"))      .count());     // There are multiple events recorded    // PrepareInstanceEvent    // BeforeTestMethodEvent    // BeforeTestExecutionEvent    // UserCreationEvent    applicationEvents.stream().forEach(System.out::println);  }}

После того, какмы выполняем публичный метод нашего класса испытываемый (createUserизUserServiceв этом примере), мы можем запросить все захваченные события из биновApplicationEvents,которые мы внедряем в наш тест.

Открытый.stream()методкласса ApplicationEventsпозволяет просмотреть все события, записанные для теста.Есть перегруженная версия .stream(),вкоторой мы запрашиваем поток только определенных событий.

Несмотря на то, что мы генерируем только одно событие из нашего приложения, Spring захватывает четыре события для теста выше.Остальные три события относятся к Spring, как иPrepareInstanceEventв среде TestContext.

Поскольку мы используем JUnit Jupiter иSpringExtension(зарегистрированный для нас при использовании@SpringBootTest), мы также можем внедритьbean-компонент ApplicationEventsв метод жизненного цикла JUnit или непосредственно в тест:

@Testvoid batchUserCreationShouldPublishEvents(@Autowired ApplicationEvents events) {  List<Long> result = this.userService.createUser(List.of("duke", "mike", "alice"));   assertEquals(3, result.size());  assertEquals(3, events.stream(UserCreationEvent.class).count());}

ЭкземплярApplicationEventsсоздается до и удаляется после каждого теста как часть текущего потока.Следовательно, вы даже можете использовать внедрение поля и@TestInstance(TestInstance.Lifecycle.PER_CLASS)делить тестовый экземпляр между несколькими тестами (PER_METHODпо умолчанию).

Обратите внимание, что запуск всего контекста Spring@SpringBootTestдля такого тестаможет быть излишним.Мы также могли бы написать тест, который заполняет минимальный SpringTestContextтолько нашимbean-компонентом UserService, чтобы убедиться, чтоUserCreationEvent опубликован:

@RecordApplicationEvents@ExtendWith(SpringExtension.class)@ContextConfiguration(classes = UserService.class)class UserServicePerClassTest {   @Autowired  private ApplicationEvents applicationEvents;   @Autowired  private UserService userService;   @Test  void userCreationShouldPublishEvent() {     this.userService.createUser("duke");     assertEquals(1, applicationEvents      .stream(UserCreationEvent.class)      .filter(event -> event.getUsername().equals("duke"))      .count());     applicationEvents.stream().forEach(System.out::println);  }}

Или используйте альтернативный подход к тестированию.

Альтернативы тестированию весенних событий

В зависимости от того, чего вы хотите достичь с помощью теста, может быть достаточно проверить эту функциональность с помощью модульного теста:

@ExtendWith(MockitoExtension.class)class UserServiceUnitTest {   @Mock  private ApplicationEventPublisher applicationEventPublisher;   @Captor  private ArgumentCaptor<UserCreationEvent> eventArgumentCaptor;   @InjectMocks  private UserService userService;   @Test  void userCreationShouldPublishEvent() {     Long result = this.userService.createUser("duke");     Mockito.verify(applicationEventPublisher).publishEvent(eventArgumentCaptor.capture());     assertEquals("duke", eventArgumentCaptor.getValue().getUsername());  }   @Test  void batchUserCreationShouldPublishEvents() {    List<Long> result = this.userService.createUser(List.of("duke", "mike", "alice"));     Mockito      .verify(applicationEventPublisher, Mockito.times(3))      .publishEvent(any(UserCreationEvent.class));  }}

Обратите внимание, что здесь мы не используем никакой поддержки Spring Test иполагаемсяисключительно наMockitoи JUnit Jupiter.

Другой подход заключается в том, чтобы не проверять события публикации явно, а проверять весь сценарий использования с помощью интеграционного теста:

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)class ApplicationIT {   @Autowired  private TestRestTemplate testRestTemplate;   @Test  void shouldCreateUserAndPerformReporting() {     ResponseEntity<Void> result = this.testRestTemplate      .postForEntity("/api/users", "duke", Void.class);     assertEquals(201, result.getStatusCodeValue());    assertTrue(result.getHeaders().containsKey("Location"),      "Response doesn't contain Location header");     // additional assertion to verify the counter was incremented    // additional assertion that a new message is part of the queue  }}

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

Резюме тестирования событий Spring с помощью Spring Boot

Все различные подходы сводятся к тестированию поведения и состояния.Благодаря новойфункции @RecordApplicationEvents вSpring Test у насможетвозникнуть соблазн провести больше поведенческих тестов и проверить внутреннюю часть нашей реализации.В общем, мы должны сосредоточиться на тестировании состояния (также известном как результат), поскольку оно поддерживает беспроблемный рефакторинг.

Представьте себе следующее: мы используем,ApplicationEventчтобы разделять части нашего приложения и гарантировать, что это событие запускается во время теста.Через две недели мы решаем убрать / переработать эту развязку (по каким-то причинам).Наш вариант использования может по-прежнему работать, как ожидалось, но наш тест теперь не проходит, потому что мы делаем предположения о технической реализации, проверяя, сколько событий мы опубликовали.

Помните об этом и не перегружайте свои тесты деталями реализации (если вы хотите провести рефакторинг в будущем :).Тем не менее, есть определенные тестовые сценарии, когда функция@RecordApplicationEvents очень помогает.

Исходный код со всеми альтернативными вариантами для тестирования Spring Event с помощью Spring Bootдоступен на GitHub.

Источник: habr.com
К списку статей
Опубликовано: 24.02.2021 18:11:35
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Java

Spring boot

Testing

Events

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru