0

This is not the same as this question: JUnit: How to simulate System.in testing?, which is about mocking stdin.

What I want to know is how to test (as in TDD) that a simple Java class with a main method waits for input.

My test:

@Test 
public void appRunShouldWaitForInput(){
    long startMillis = System.currentTimeMillis();
    // NB obviously you'd want to run this next line in a separate thread with some sort of timeout mechanism... 
    // that's an implementation detail I've omitted for the sake of avoiding clutter!
    App.main( null );
    long endMillis = System.currentTimeMillis();
    assertThat( endMillis - startMillis ).isGreaterThan( 1000L );
}

My SUT main:

public static void main(String args[]) {
    BufferedReader br = null;
    try {
        br = new BufferedReader(new InputStreamReader(System.in));
        System.out.print("Enter something : ");
        String input = br.readLine();
    } catch (IOException e) {
        e.printStackTrace();
    } 

... test fails. The code does not wait. But when you run the app at the command prompt it does indeed wait.

NB by the way I did also try with setting stdin to sthg else:

System.setIn(new ByteArrayInputStream( dummy.getBytes()));
scanner = new Scanner(System.in);

... this did not hold up the test.

Community
  • 1
  • 1
mike rodent
  • 10,479
  • 10
  • 80
  • 104
  • If it *did* wait for input, your test would wait forever. Also, do you really think that's a good test? It's not testing *your* code, it's testing `BufferedReader`. – Kayaman Sep 22 '16 at 19:47
  • If you really want to test `BufferedReader`, then the post you linked is what you need. Just remember that the idea of TDD or any testing isn't to test code that's developed (and hopefully tested) by others. Testing whether `readLine()` blocks until it receives a `\n` is quite frankly ridiculous. – Kayaman Sep 22 '16 at 20:00
  • (slightly modified to show what I'm trying to do). I don't think this is about testing `BufferedReader`. A `main` method without the line `br.readLine()` returns immediately: so I need a test to justify including that line. If that line of app code was subsequently dropped or not executed for some reason I'd want this test to fail. The simple fact is that `br.readLine()` definitely definitely does NOT block when the app class is run from my test method. Why not? – mike rodent Sep 22 '16 at 20:04
  • Because `System.in` isn't what you expect standard input to be when running a CLI program normally. I'd imagine it's null. – Kayaman Sep 22 '16 at 20:07
  • Thanks for your continued interest... I don't think this is quite right, though: see my added code: I have tried setting `stdin` to sthg else... – mike rodent Sep 22 '16 at 20:09
  • `ByteArrayInputStream` won't do it. It won't block. It will just reach EOS and that's why there's no waiting. – Kayaman Sep 22 '16 at 20:14
  • Ah... OK, thanks. I wonder whether there are any `InputStream` classes which can block in the way `stdin` does then? – mike rodent Sep 22 '16 at 20:16
  • No, you'll have to create your own. But that has the additional benefit that you can make it block for a certain time, and then your test won't hang. – Kayaman Sep 22 '16 at 20:18
  • Thanks again... just found `org.apache.poi.util.BlockingInputStream`... which suggests adding a `timeout`, indeed. We're being told to stop now, but thanks again! – mike rodent Sep 22 '16 at 20:20

2 Answers2

1

As a much more general rule, static methods (such as main methods) are difficult to test. For this reason, you almost never call the main method (or any other static method) from your test code. A common pattern to work around this is to convert this:

public class App {
    public static void main(String args[]) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(System.in));
            System.out.print("Enter something : ");
            String input = br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }
}

to this:

public class App {
    private final InputStream input;
    private final OutputStream output;

    public App(InputStream input, OutputStream output) {
        this.input = input;
        this.output = output;
    }

    public static void main(String[] args) {
        new App(System.in, System.out).start();
    }

    public void start() {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(input));
            output.print("Enter something : ");
            String nextInput = br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }
}

Now your test becomes this:

@Test 
public void appRunShouldWaitForInput(){
    ByteArrayOutputStream output = new ByteArrayOutputStream();

    // As you have already noted, you would need to kick this off on another thread and use a blocking implementation of InputStream to test what you want to test.
    new App(new ByteArrayInputStream(), output).start();

    assertThat(output.toByteArray().length, is(0));
}

The key idea is that, when you run the app "for real" i.e. via the main method, it will use the Standard input and output streams. However, when you run it from your tests, it uses a purely in memory input/output stream which you have full control over in your test. ByteArrayOutputStream is just one example, but you can see in my example that the test is able to inspect the actual bytes that have been written to the output stream.

tonicsoft
  • 1,539
  • 6
  • 16
  • Your answer taught me a lot ... although finally I think I've come to the conclusion that this blocking of System.in (wrapped in a BufferedReader) has 1) been tested extensively and must be trusted and 2) cannot in fact be tested in test code, unless you use System.in itself. I'm a struggling TDD newb... :) – mike rodent Oct 29 '16 at 18:02
  • You've got it spot on. It can't be tested in a Unit Test, but you *could* write a larger scale "Acceptance Test" that will actually run your program in a different process and interacts with it via the std in and std err, but this will be quite cumbersome and in general you should have a very small number of this style of test compared to pure unit test. – tonicsoft Oct 31 '16 at 08:43
1

in general: you can't test if your program is waiting until something happens (because you can't test if it waits forever)

what is usually done in such case:

  1. in your case: don't test it. readLine is provided by external library that was tested pretty intensive. cost of testing this is much higher then the value of such test. refactor and test your business code (string/stream operations), not infrastructure (system input)

  2. in general case it's testing concurrent programming. it's hard so ppl usually try to find some useful simplification:

    1. you can just test if output of your program is correct for the input provided in tests.
    2. for some really hard problems above technique is combined with running test thousands times (and forcing thread switching if possible) to detect errors in concurrent programming (violated invariants)
    3. before your test provides an input date, you can test if your program is in correct state (waiting thread, correct field values etc)
    4. in worst case, you can use delay in tests before providing input to be sure, your program waits and uses the input. this technique is often used because it's simple but it's smelly and if you add more tests like that your whole suit gets slower
piotrek
  • 12,111
  • 8
  • 63
  • 144