JUnit: How to simulate System.in testing?

2019-01-04 01:06发布

I have a Java command-line program. I would like to create JUnit test case to be able to simulate System.in. Because when my program runs it will get into the while loop and waits for input from users. How do I simulate that in JUnit?

Thanks

8条回答
放我归山
2楼-- · 2019-01-04 01:29

You can write a clear test for the command line interface by using the TextFromStandardInputStream rule of the System Rules library.

public void MyTest {
  @Rule
  public final TextFromStandardInputStream systemInMock
    = emptyStandardInputStream();

  @Test
  public void readTextFromStandardInputStream() {
    systemInMock.provideLines("foo");
    Scanner scanner = new Scanner(System.in);
    assertEquals("foo", scanner.nextLine());
  }
}

Full disclosure: I'm the author of that library.

查看更多
爷、活的狠高调
3楼-- · 2019-01-04 01:31

The problem with BufferedReader.readLine() is that it is a blocking method which waits for user input. It seems to me that you don't particularly want to simulate that (i.e. you want tests to be fast). But in a testing context it continually returns null at high speed during testing, which is irksome.

For a purist you can make the getInputLine below package-private, and mock it: easy-peezy.

String getInputLine() throws Exception {
    return br.readLine();
}

... you'd have to make sure that you had a way of stopping (typically) a loop of user interaction with the app. You'd also have to cope with the fact that your "input lines" would always be the same until you somehow changed the doReturn of your mock: hardly typical of user input.

For a non-purist who wishes to make life easy for themselves (and produce readable tests) you could put all this stuff below in your app code:

private Deque<String> inputLinesDeque;

void setInputLines(List<String> inputLines) {
    inputLinesDeque = new ArrayDeque<String>(inputLines);
}

private String getInputLine() throws Exception {
    if (inputLinesDeque == null) {
        // ... i.e. normal case, during app run: this is then a blocking method
        return br.readLine();
    }
    String nextLine = null;
    try {
        nextLine = inputLinesDeque.pop();
    } catch (NoSuchElementException e) {
        // when the Deque runs dry the line returned is a "poison pill", 
        // signalling to the caller method that the input is finished
        return "q";
    }

    return nextLine;
}

... in your test you might then go like this:

consoleHandler.setInputLines( Arrays.asList( new String[]{ "first input line", "second input line" }));

before triggering off the method in this "ConsoleHandler" class which needs input lines.

查看更多
登录 后发表回答