Skip to content

Commit 9dbdd71

Browse files
committed
Refine #1323
- Encapsulate command name in ShellInputProvider - Rename builder methods for better readability - Update Javadocs - Update reference docs - Add missing headers Issue #1292
1 parent 08be1ae commit 9dbdd71

File tree

4 files changed

+120
-30
lines changed

4 files changed

+120
-30
lines changed

spring-shell-docs/modules/ROOT/pages/testing.adoc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,42 @@ class ShellTestIntegrationTests {
6969
}
7070
----
7171

72+
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.
73+
74+
For example, if your command expects input as follows:
75+
76+
[source,java]
77+
----
78+
@Bean
79+
public Command ask() {
80+
return new AbstractCommand("ask", "Ask for user input") {
81+
@Override
82+
public ExitStatus doExecute(CommandContext commandContext) throws Exception {
83+
String message = commandContext.inputReader().readInput();
84+
commandContext.outputWriter().println("You said: " + message);
85+
return ExitStatus.OK;
86+
}
87+
};
88+
}
89+
----
90+
91+
then you can simulate the user's input by using `ShellInputProvider` to supply the necessary input during testing:
92+
93+
[source,java]
94+
----
95+
@Test
96+
void testCommandExecutionWithReadingInput(@Autowired ShellTestClient client) throws Exception {
97+
// given
98+
ShellInputProvider inputProvider = ShellInputProvider.providerFor("ask").withInput("hi").build();
99+
100+
// when
101+
ShellScreen screen = client.sendCommand(inputProvider);
102+
103+
// then
104+
ShellAssertions.assertThat(screen).containsText("You said: hi");
105+
}
106+
----
107+
72108
== End-to-End Testing
73109

74110
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:

spring-shell-test/src/main/java/org/springframework/shell/test/ShellInputProvider.java

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
/*
2+
* Copyright 2026-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
116
package org.springframework.shell.test;
217

318
import java.util.ArrayList;
@@ -6,42 +21,57 @@
621
import java.util.List;
722

823
/**
9-
* Represents a provider for inputs and passwords used in shell interactions. This class
10-
* uses two {@link Deque} instances to manage the queue of user inputs and passwords. It
11-
* serves as a utility for simulating user input during testing or automated shell
12-
* interactions.
24+
* Represents a provider for commands with their inputs and passwords used in shell
25+
* interactions. This class uses two {@link Deque} instances to manage the queue of user
26+
* inputs and passwords. It serves as a utility for simulating user input during testing
27+
* or automated shell interactions.
1328
*
29+
* @param command represents the command to be executed in the shell.
1430
* @param inputs Queue containing user input strings.
1531
* @param passwords Queue containing user password strings.
32+
* @author David Pilar
33+
* @author Mahmoud Ben Hassine
34+
* @since 4.0.2
1635
*/
17-
public record ShellInputProvider(Deque<String> inputs, Deque<String> passwords) {
36+
public record ShellInputProvider(String command, Deque<String> inputs, Deque<String> passwords) {
1837

19-
static Builder builder() {
20-
return Builder.builder();
38+
/**
39+
* Creates a new input provider for the specified command.
40+
* @param command the command for which the input provider is to be created
41+
* @return a new {@link Builder} instance initialized with the given command
42+
*/
43+
static Builder providerFor(String command) {
44+
return Builder.builder(command);
2145
}
2246

2347
final static class Builder {
2448

49+
private final String command;
50+
2551
private final List<String> inputs = new ArrayList<>();
2652

2753
private final List<String> passwords = new ArrayList<>();
2854

29-
static Builder builder() {
30-
return new Builder();
55+
Builder(String command) {
56+
this.command = command;
57+
}
58+
59+
static Builder builder(String command) {
60+
return new Builder(command);
3161
}
3262

33-
public Builder input(String... input) {
63+
public Builder withInput(String... input) {
3464
inputs.addAll(List.of(input));
3565
return this;
3666
}
3767

38-
public Builder password(String... password) {
68+
public Builder withPassword(String... password) {
3969
passwords.addAll(List.of(password));
4070
return this;
4171
}
4272

4373
public ShellInputProvider build() {
44-
return new ShellInputProvider(new LinkedList<>(inputs), new LinkedList<>(passwords));
74+
return new ShellInputProvider(command, new LinkedList<>(inputs), new LinkedList<>(passwords));
4575
}
4676

4777
}

spring-shell-test/src/main/java/org/springframework/shell/test/ShellTestClient.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616
package org.springframework.shell.test;
1717

1818
import org.springframework.shell.core.InputReader;
19-
import org.springframework.shell.core.command.*;
19+
import org.springframework.shell.core.command.CommandContext;
20+
import org.springframework.shell.core.command.CommandExecutor;
21+
import org.springframework.shell.core.command.CommandParser;
22+
import org.springframework.shell.core.command.CommandRegistry;
23+
import org.springframework.shell.core.command.ParsedInput;
2024

2125
import java.io.PrintWriter;
2226
import java.io.StringWriter;
@@ -57,35 +61,36 @@ public ShellTestClient(CommandParser commandParser, CommandRegistry commandRegis
5761
* @throws Exception if an error occurred during command execution
5862
*/
5963
public ShellScreen sendCommand(String input) throws Exception {
60-
return sendCommand(input, ShellInputProvider.builder().build());
64+
return sendCommand(ShellInputProvider.providerFor(input).build());
6165
}
6266

6367
/**
6468
* Sends a command to the shell, processes user inputs or passwords as needed, and
6569
* returns the resulting shell screen output.
66-
* @param input the raw command string to be sent to the shell
67-
* @param inputProvider a provider containing simulated user inputs and passwords
70+
* @param inputProvider a provider containing simulated user's command with inputs and
71+
* passwords
6872
* @return a {@code ShellScreen} object containing the output of the shell after
6973
* command execution
7074
* @throws Exception if an error occurs during the command execution
75+
* @since 4.0.2
7176
*/
72-
public ShellScreen sendCommand(String input, ShellInputProvider inputProvider) throws Exception {
77+
public ShellScreen sendCommand(ShellInputProvider inputProvider) throws Exception {
7378
StringWriter stringWriter = new StringWriter();
74-
ParsedInput parsedInput = this.commandParser.parse(input);
79+
ParsedInput parsedInput = this.commandParser.parse(inputProvider.command());
7580
PrintWriter outputWriter = new PrintWriter(stringWriter);
7681
InputReader inputReader = new InputReader() {
7782
@Override
7883
public String readInput(String prompt) {
7984
outputWriter.print(prompt);
80-
String in = inputProvider.inputs().pollFirst();
81-
return in == null ? "" : in;
85+
String input = inputProvider.inputs().pollFirst();
86+
return input == null ? "" : input;
8287
}
8388

8489
@Override
8590
public char[] readPassword(String prompt) {
8691
outputWriter.print(prompt);
87-
String pwd = inputProvider.passwords().pollFirst();
88-
return pwd == null ? new char[] {} : pwd.toCharArray();
92+
String password = inputProvider.passwords().pollFirst();
93+
return password == null ? new char[] {} : password.toCharArray();
8994
}
9095
};
9196
CommandContext commandContext = new CommandContext(parsedInput, this.commandRegistry, outputWriter,

spring-shell-test/src/test/java/org/springframework/shell/test/ShellTestClientTests.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
/*
2+
* Copyright 2026-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
116
package org.springframework.shell.test;
217

318
import org.assertj.core.api.Assertions;
@@ -17,8 +32,6 @@
1732
import org.springframework.shell.core.command.ExitStatus;
1833
import org.springframework.test.context.junit.jupiter.SpringExtension;
1934

20-
import java.io.PrintWriter;
21-
2235
@ExtendWith(SpringExtension.class)
2336
class ShellTestClientTests {
2437

@@ -40,17 +53,23 @@ void testUnknownCommandExecution(@Autowired ShellTestClient shellTestClient) {
4053

4154
@Test
4255
void testCommandExecutionWithReadingInput(@Autowired ShellTestClient client) throws Exception {
56+
// given
57+
ShellInputProvider inputProvider = ShellInputProvider.providerFor("hello").withInput("hi").build();
58+
4359
// when
44-
ShellScreen screen = client.sendCommand("hello", ShellInputProvider.builder().input("hi").build());
60+
ShellScreen screen = client.sendCommand(inputProvider);
4561

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

5066
@Test
5167
void testCommandExecutionWithReadingPassword(@Autowired ShellTestClient client) throws Exception {
68+
// given
69+
ShellInputProvider inputProvider = ShellInputProvider.providerFor("password").withPassword("secret123").build();
70+
5271
// when
53-
ShellScreen screen = client.sendCommand("password", ShellInputProvider.builder().password("secret123").build());
72+
ShellScreen screen = client.sendCommand(inputProvider);
5473

5574
// then
5675
ShellAssertions.assertThat(screen).containsText("Your password is: secret123");
@@ -59,13 +78,13 @@ void testCommandExecutionWithReadingPassword(@Autowired ShellTestClient client)
5978
@Test
6079
void testCommandExecutionWithComplexInputs(@Autowired ShellTestClient client) throws Exception {
6180
// given
62-
ShellInputProvider inputProvider = ShellInputProvider.builder()
63-
.input("One", "Two")
64-
.password("secret1", "secret2")
81+
ShellInputProvider inputProvider = ShellInputProvider.providerFor("complex")
82+
.withInput("One", "Two")
83+
.withPassword("secret1", "secret2")
6584
.build();
6685

6786
// when
68-
ShellScreen screen = client.sendCommand("complex", inputProvider);
87+
ShellScreen screen = client.sendCommand(inputProvider);
6988

7089
// then
7190
ShellAssertions.assertThat(screen).containsText("First input is: One");

0 commit comments

Comments
 (0)