Testing

Spring Shell provides several utilities to facilitate testing of shell applications. These utilities help simulate user input, capture output, and verify command behavior in a controlled environment.

Test assertions

Spring Shell offers the following test assertion APIs to validate command execution and output:

  • ShellScreen: This class represents the shell screen and allows you to capture and analyze the output displayed to the user.

  • ShellAssertions: This class provides static methods to assert the results of shell command executions.

  • ShellTestClient: This class allows you to simulate user input and execute shell commands programmatically.

Here is an example of how to use these APIs in a test:

@ExtendWith(SpringExtension.class)
class ShellTestClientTests {

	@Test
	void testCommandExecution(@Autowired ShellTestClient shellTestClient) throws Exception {
		// when
		ShellScreen shellScreen = shellTestClient.sendCommand("test");

		// then
		ShellAssertions.assertThat(shellScreen).containsText("Test command executed");
	}
}

Test annotations

Spring Shell provides the @ShellTest annotation which is used to indicate that a test class is a Spring Shell test. It sets up the necessary context for testing shell commands. This annotation is defined in the spring-shell-test-autoconfigure module, and is designed to be used with Spring Boot applications.

Once you define your Spring Boot application class, you can create a test class annotated with @ShellTest to test your shell commands. Here is an example:

@SpringBootApplication
public class ExampleShellApplication {

	@Command(name = "hi", description = "Says hello")
	public String hello() {
		return "hello";
	}

}

@ShellTest
@ContextConfiguration(classes = ExampleShellApplication.class)
class ShellTestIntegrationTests {

	@Test
	void testCommandExecution(@Autowired ShellTestClient client) throws Exception {
		// when
		ShellScreen shellScreen = client.sendCommand("hi");

		// then
		ShellAssertions.assertThat(shellScreen).containsText("hello");
	}

	@Test
	void testUnknownCommandExecution(@Autowired ShellTestClient client) {
		Assertions.assertThatThrownBy(() -> client.sendCommand("foo"))
                  .isInstanceOf(CommandNotFoundException.class);
	}

}

Spring Shell also provides the ShellInputProvider API which can be used to provide custom input for testing purposes. This allows you to simulate user input (clear texts and passwords) in a more flexible way.

For example, if your command expects input as follows:

@Bean
public Command ask() {
    return new AbstractCommand("ask", "Ask for user input") {
        @Override
        public ExitStatus doExecute(CommandContext commandContext) throws Exception {
            String message = commandContext.inputReader().readInput();
            commandContext.outputWriter().println("You said: " + message);
            return ExitStatus.OK;
        }
    };
}

then you can simulate the user’s input by using ShellInputProvider to supply the necessary input during testing:

@Test
void testCommandExecutionWithReadingInput(@Autowired ShellTestClient client) throws Exception {
    // given
    ShellInputProvider inputProvider = ShellInputProvider.providerFor("ask").withInput("hi").build();

    // when
    ShellScreen screen = client.sendCommand(inputProvider);

    // then
    ShellAssertions.assertThat(screen).containsText("You said: hi");
}

End-to-End Testing

For end-to-end (ie black-box) testing of shell applications without using Spring Shell testing facilities, you can use the test utilities provided by Spring Boot. Once you have defined your Spring Boot application class, you can create a test class annotated with @SpringBootTest to test your shell commands. Here is an example:

@SpringBootApplication
public class MyShellApplication {

	public static void main(String[] args) {
		SpringApplication.run(MyShellApplication.class, args);
	}

	@Command
	public void hi() {
		System.out.println("Hello world!");
	}

}

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
		useMainMethod = SpringBootTest.UseMainMethod.ALWAYS,
		classes = { MyShellApplication.class },
		properties = { "spring.shell.interactive.enabled=false" },
        args = "hi")
@ExtendWith(OutputCaptureExtension.class)
public class ShellApplicationEndToEndTests {

	@Test
	void testCommandOutput(CapturedOutput output) {
		assertThat(output).contains("Hello world!");
	}

}

This example demonstrates how to run a Spring Shell application in a test context and verify the output of a command using Spring Boot’s OutputCaptureExtension.

The caveat with this approach is that it is not convenient for testing multiple commands in the same test class due to the fact that the command is passed through the args property of the @SpringBootTest annotation. For testing multiple commands, it is recommended to use the Spring Shell testing utilities as described in the previous sections. Here is an example of how to test multiple commands using the Spring Shell testing utilities:

@SpringBootApplication
public class GreetingShellApplication {

	public static void main(String[] args) {
		SpringApplication.run(GreetingShellApplication.class, args);
	}

	@Command
	public void hi(CommandContext context) {
		context.outputWriter().println("Hello world!");
	}

	@Command
	public void bye(CommandContext context) {
		context.outputWriter().println("Goodbye world!");
	}

}

@ShellTest
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
		useMainMethod = SpringBootTest.UseMainMethod.ALWAYS, classes = { GreetingShellApplication.class },
		properties = { "spring.shell.interactive.enabled=false" })
public class ShellApplicationEndToEndMultipleCommandsTests {

	@Test
	void testCommandExecution(@Autowired ShellTestClient client) throws Exception {
		// when
		ShellScreen shellScreen = client.sendCommand("help");

		// then
		ShellAssertions.assertThat(shellScreen).containsText("AVAILABLE COMMANDS");
	}

	@Test
	void testHiCommandExecution(@Autowired ShellTestClient client) throws Exception {
		// when
		ShellScreen shellScreen = client.sendCommand("hi");

		// then
		ShellAssertions.assertThat(shellScreen).containsText("Hello world!");
	}

	@Test
	void testByeCommandExecution(@Autowired ShellTestClient client) throws Exception {
		// when
		ShellScreen shellScreen = client.sendCommand("bye");

		// then
		ShellAssertions.assertThat(shellScreen).containsText("Goodbye world!");
	}

}