Testing System.in and System.out with system-rules
Writing unit tests is an integral part of software development. One problem you have to solve when your class under test interacts with the operating system, is to simulate its behaviours. This can be done by using mocks instead of the real objects provided by the Java Runtime Environment (JRE). Libraries that support mocking for Java are for example mockito or jMock.
Mocking objects is a great thing when you have complete control over their instantiation. When dealing with standard input and standard output this is a little bit tricky, but not impossible as java.lang.System
lets you replace the standard InputStream
and OutputStream
.
System.setIn(in); System.setOut(out);
In order that you do not have to replace the streams before and after each test case manually, you can utilize org.junit.rules.ExternalResource
. This class provides the two methods before()
and after()
that are called, like their names suggest, before and after each test case. This way you can easily setup and cleanup resources that all of your tests within one class need. Or, to come back to the original problem, replace the input and output stream for java.lang.System
.
Exactly what I have described above, is implemented by the library system-rules. To see how it works, lets start with a simple example:
public class CliExample { private Scanner scanner = new Scanner(System.in, "UTF-8"); public static void main(String[] args) { CliExample cliExample = new CliExample(); cliExample.run(); } private void run() { try { int a = readInNumber(); int b = readInNumber(); int sum = a + b; System.out.println(sum); } catch (InputMismatchException e) { System.err.println("The input is not a valid integer."); } catch (IOException e) { System.err.println("An input/output error occurred: " + e.getMessage()); } } private int readInNumber() throws IOException { System.out.println("Please enter a number:"); String nextInput = scanner.next(); try { return Integer.valueOf(nextInput); } catch(Exception e) { throw new InputMismatchException(); } } }
The code above reads two intergers from standard input and prints out its sum. In case the user provides an invalid input, the program should output an appropriate message on the error stream.
In the first test case, we want to verify that the program correctly sums up two numbers and prints out the result:
public class CliExampleTest { @Rule public final StandardErrorStreamLog stdErrLog = new StandardErrorStreamLog(); @Rule public final StandardOutputStreamLog stdOutLog = new StandardOutputStreamLog(); @Rule public final TextFromStandardInputStream systemInMock = emptyStandardInputStream(); @Test public void testSuccessfulExecution() { systemInMock.provideText("2\n3\n"); CliExample.main(new String[]{}); assertThat(stdOutLog.getLog(), is("Please enter a number:\r\nPlease enter a number:\r\n5\r\n")); } ... }
To simulate System.in
we utilize system-rules’ TextFromStandardInputStream
. The instance variable is initialized with an empty input stream by calling emptyStandardInputStream()
. In the test case itself we provide the intput for the application by calling provideText()
with a newline at the appropriate points. Then we call the main()
method of our application. Finally we have to assert that the application has written the two input statements and the result to the standard input. The latter is done through an instance of StandardOutputStreamLog
. By calling its method getLog()
we retrieve everything that has been written to standard output during the current test case.
The StandardErrorStreamLog
can be used alike for the verification of what has been written to standard error:
@Test public void testInvalidInput() throws IOException { systemInMock.provideText("a\n"); CliExample.main(new String[]{}); assertThat(stdErrLog.getLog(), is("The input is not a valid integer.\r\n")); }
Beyond that system-rules
also offers rules for the work with System.getProperty()
, System.setProperty()
, System.exit()
and System.getSecurityManager()
.
Conclusion: With system-rules testing command line applications with unit tests becomes even more simpler than using junit’s Rules itself. All the boilerplate code to update the system environment before and after each test case comes within some easy to use rules.
PS: You can find the complete sources here.
Reference: | Testing System.in and System.out with system-rules from our JCG partner Martin Mois at the Martin’s Developer World blog. |