Writing @SpringBootTest tests when using Spring Shell in an application

Sometimes in applications it is useful to have a console for managing the application directly from the server. One of the extremely convenient solutions to this problem is Spring Shell.


Tests are also a very good practice (I hope you have them) and, sometimes, they are written with the @SpringBootTest annotation. However, if you plug in Spring Shell and try to run such a test, then ... your test will simply freeze in anticipation of a command from the console.


So, we are going in search of a solution.

Google


After a short search on GitHub, we find a similar problem .


For testing the shell, the author suggests overriding the bin with the ApplicationRunner type, which expects a command from the console. Here is the solution for accessing and testing the commands themselves defined in @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);
		
		
	}
	
	
}

Unfortunately, the tests with this solution still hang waiting for the team.

It's time to look under the hood!


After a light debug, we find the following code in the SpringApplication class:


	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);
			}
		}
	}

In other words, Spring Boot simply adds our bin with custom ApplicationRunner to the already defined ones and launches them all.

It would seem that the solution is simple - redefine the bin! Time to get into the Spring Shell source.


Redefine bean


It quickly becomes clear that the JLineShellAutoConfiguration class is responsible for creating the runners, specifically we are interested in the scriptApplicationRunner bean, which prevents our test from starting.


, ( 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;

Wow, everything seems clear now - go back to application.properties and write:


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

Cross your fingers. Run the test. Works.


The case is solved, thank you for your attention.


All Articles