Написание тестов @SpringBootTest при ипользовании Spring Shell в приложении

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

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


Тесты — тоже весьма неплохая практика (надеюсь у вас они есть) и, иногда, они пишутся с аннотацией @SpringBootTest. Однако, если вы подключите Spring Shell и попробуете запустить такой тест, то… ваш тест просто зависнет в ожидании введения команды с консоли.


Итак, отправляемся на поиски решения.

Гуглим


После недолгого поиска на GitHub находим похожую проблему.


Автор предлагает для тестирования shell-а переопределить бин с типом ApplicationRunner, который и ожидает команды с консоли. Здесь же решение по доступу и тестированию самих команд определенных в @ShellComponent.


@Component
public class CliAppRunner implements ApplicationRunner {
	public CliAppRunner() {

	}

	@Override
	public void run(ApplicationArguments args) throws Exception {
		//do nothing
	}
	

}

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes =CliConfig.class)
public class ShellCommandIntegrationTest {

	@Autowired
	private Shell shell;
	
	@Test
	public void runTest(){
		
		
		Object result=shell.evaluate(new Input(){
			@Override
			public String rawText() {
				return "add 1 3";
			}
			
		});

		DefaultResultHandler  resulthandler=new DefaultResultHandler();
		resulthandler.handleResult(result);
		
		
	}
	
	
}

К сожалению тесты при таком решении все равно зависают в ожидании команды.

Пришло время заглянуть под капот!


После легкого дебага находим в классе SpringApplication следующий код:


	private void callRunners(ApplicationContext context, ApplicationArguments args) {
		List<Object> runners = new ArrayList<>();
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		AnnotationAwareOrderComparator.sort(runners);
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}

Иначе говоря, Spring Boot просто добавляет наш бин с кастомным ApplicationRunner в копилку к уже определенным и запускает их все.

Казалось бы решение простое — переопределим бин! Время залезть в исходники Spring Shell.


Переопределяем бин


Быстро выясняется, что за создание раннеров отвечает класс JLineShellAutoConfiguration, конкретно нас интересует бин scriptApplicationRunner, который и не дает нашему тесту запуститься.


Ок, переопределим его в нашем тестовом классе (не забыв включить spring.main.allow-bean-definition-overriding=true для Spring 2.+):


	
       @TestConfiguration
	static class Runner {
		@Bean
		public ApplicationRunner scriptApplicationRunner(){
			return new CliAppRunner();
		}
	}

Нет, опять не сработало. JLineShellAutoConfiguration подгружается позже нашей тестовой конфигурации Runner и успешно переопределяет scriptApplicationRunner. И тест опять не запускается (Небольшой интерактив — кто-нибудь — объясните в комментариях, почему так?).


Ищем другие варианты


Что ж, посмотрим, что там написано в создании бина в JLineShellAutoConfiguration:


	@Bean
	@ConditionalOnProperty(prefix = SPRING_SHELL_SCRIPT, value = ScriptShellApplicationRunner.ENABLED, havingValue = "true", matchIfMissing = true)
	public ApplicationRunner scriptApplicationRunner(Parser parser, ConfigurableEnvironment environment) {
		return new ScriptShellApplicationRunner(parser, shell, environment);
	}

Ура, нам повезло — есть property, который позволяет его отключить. Радостно бежим вписывать его в application.properties:


spring.shell.script.enabled=false 

Запускаем наш тест. И он опять зависает. Копаем дальше.


Разгадка


Идем в ScriptShellApplicationRunner и смотрим, что там с нашими property. А там:


	public static final String SPRING_SHELL_SCRIPT = "spring.shell.script";
	public static final String ENABLED = "spring.shell.script";

	/**
	 * The name of the environment property that allows to disable the behavior of this
	 * runner.
	 */
	public static final String SPRING_SHELL_SCRIPT_ENABLED = SPRING_SHELL_SCRIPT + "." + ENABLED;

Воу, кажется теперь все понятно — идем снова в application.properties и пишем:


spring.shell.script.spring.shell.script=false 

Скрестим пальцы. Запускаем тест. Работает.


Дело раскрыто, спасибо за внимание.

Источник: https://habr.com/ru/post/502204/


Интересные статьи

Интересные статьи

Веб-приложение часто содержит API для взаимодействия с ним. Документирование API позволит клиентам быстрее понять, как использовать ваши сервисы. Если API закрыт от внешнего мира, то все ...
Запуская свои первые эксперименты, я считала, что все эти «три / пять / семь самых популярных ляпов», о которых читала в статьях и слушала на конференциях — уж точно не про меня. Тем ...
28 июля приглашаем на онлайн-лекцию о разработке приложений на Java Spring, Quarkus, Vert.x и GraalVM с деплоем в MicroK8s. В программе лекции: Сергей Кошкинов и Андрей Смирнов...
Много всякого сыпется в мой ящик, в том числе и от Битрикса (справедливости ради стоит отметить, что я когда-то регистрировался на их сайте). Но вот мне надоели эти письма и я решил отписатьс...
В этой статье вы узнаете, как с помощью Speedment создать полный CRUD REST API для базы данных. С каждым годом становится все очевиднее, что Spring Framework является одним из наиболее шир...