[Java] Запись событий Spring при тестировании приложений Spring Boot (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Одна из основных функций 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 создает и хранит наших новых пользователей. Мы можем создать как одного пользователя, так и группу пользователей:
@Service
public 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:
@Component
public 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
@RecordApplicationEvents
class 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 или непосредственно в тест:
@Test
void 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 для такого теста может быть излишним. Мы также могли бы написать тест, который заполняет минимальный Spring TestContext только нашим 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
===========
===========
Автор оригинала: Philip Riecks
===========Похожие новости:
- [Высокая производительность, JavaScript, Программирование, Клиентская оптимизация, TypeScript] JavaScript нанобенчмарки и преждевременные тормоза
- [Java] Методы расширения в Java
- [Java, Параллельное программирование] Реактивное программирование на Java: как, зачем и стоит ли? Часть I
- [Программирование, Java, Совершенный код, C#, Kotlin] Лучший язык программирования
- [Open source, Java, Scala, Apache, Natural Language Processing] Программируемые NER компоненты
- [JavaScript, ООП] Создание квадратизированной галереи проектов на JS v 2.0
- [Ненормальное программирование, JavaScript, Разработка игр] Свежий взгляд на честное 3D в браузере
- [JavaScript, Программирование] Полезные расширения VS Code для JavaScript-разработчиков (перевод)
- [JavaScript, Программирование, Управление проектами, Управление продуктом, Карьера в IT-индустрии] Pet-проект для джуна. Или зачем и как выбрать pet project. (+личный опыт)
- [Информационная безопасность, Антивирусная защита, Разработка под MacOS] На 30 тысячах компьютеров с macOS нашли странный зловред, который ждёт команду
Теги для поиска: #_java, #_spring_boot, #_testing, #_events, #_java
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 18:10
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Одна из основных функций 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> 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 } @Service
public 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; } } @Component
public 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); } } @SpringBootTest
@RecordApplicationEvents class 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); } } @Test
void 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()); } @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)); } } @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 } } =========== Источник: habr.com =========== =========== Автор оригинала: Philip Riecks ===========Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 18:10
Часовой пояс: UTC + 5