Core Java

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.

Martin Mois

Martin is a Java EE enthusiast and works for an international operating company. He is interested in clean code and the software craftsmanship approach. He also strongly believes in automated testing and continuous integration.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button