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 {
}
}
@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";
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.