Search code examples
javajunitconsoleinputstream

Empty InputStream after console I/O redirection


I have to test a program, where user have to enter 3 numbers and then he get result in console output.

I wrote test and redirected System.in and System.out. My test cases look like this:

private static ByteArrayOutputStream mockOut;

@Test
public void correct_input_test() {
    test("> x = 0, y = 0", 1, 0, 0);
    test("> x = -1, y = 0", 1, 0, -1);
}

Here's expected console output as first argument and 3 int args to prepare input stream.

test() method is below:

private static void test(String expected, int ... args) {
    set(args);       // preparing inputstream and redirecting Sys.i/o
    run(expected);   // running program test after setting up
}

As many integer inputs are allowed as needed. These args passed further to set() method where I create an Input stream and redirect System.in set() method:

private static void set(int... args) {
    String s = Arrays.stream(args)
               .mapToObj(String::valueOf)
               .collect(Collectors.joining(" "));          // assembling args String
    System.setIn(new ByteArrayInputStream(s.getBytes()));  // redirecting Sys.in 
    mockOut = new ByteArrayOutputStream();
    PrintStream stream = new PrintStream(mockOut);
    System.setOut(stream);                                 // redirecting Sys.out
}

Passed arguments 1, 0, 0 became a String s = "1 0 0"

And finally after set I run method that invokes my program

private static void run(String expected) {
    Main.main(null);                                              // call program to catch result
    String[] consoleLines = mockOut.toString().split("\n");       // split console output
    assertEquals(expected, consoleLines[consoleLines.length-1]);  // compare lines
}

Here I split on separate lines console output that I get in mockOut object and compare the last line with expected string.

It works only if I test one testcase (if I comment out any of these testcases).

@Test
public void correct_input_test() {
//    test("> x = 0, y = 0", 1, 0, 0);
    test("> x = -1, y = 0", 1, 0, -1);
}

But if I try to run both after the second try to invoke program it gets an empty input stream with java.util.NoSuchElementException and I actually don't know why. I've tried to close streams but I guess it doesn't matter. Each time I call set() I create new InputStream with non-empty args-string. I also tried to restore default System.in and redirect it again in set() - no effect.

Could anyone explain what exactly did I forget?

UPD. I Separated my code to methods to explain which is which but in case it will increase readability - here is whole same code below:

private static ByteArrayOutputStream mockOut;

@Test
public void correct_input_test() {
    test("> x = 0, y = 0", 1, 0, 0);
    test("> x = -1, y = 0", 1, 0, -1);
}

private static void test(String expected, int ... args) {
    set(args);       // preparing inputstream and redirecting Sys.i/o
    run(expected);   // running program test after setting up
}

private static void set(int... args) {
    String s = Arrays.stream(args)
               .mapToObj(String::valueOf)
               .collect(Collectors.joining(" "));          // assembling args String
    System.setIn(new ByteArrayInputStream(s.getBytes()));  // redirecting Sys.in 
    mockOut = new ByteArrayOutputStream();
    PrintStream stream = new PrintStream(mockOut);
    System.setOut(stream);                                 // redirecting Sys.out
}

private static void run(String expected) {
    Main.main(null);                                              // call program to catch result
    String[] consoleLines = mockOut.toString().split("\n");       // split console output
    assertEquals(expected, consoleLines[consoleLines.length-1]);  // compare lines
}

UPD. Here is stack trace:

java.util.NoSuchElementException: 

Arguments input incomplete.

    at Main.in(Main.java:169)
    at Main.main(Main.java:19)
    at MainTest$MainMethodTest.run(MainTest.java:75)
    at MainTest$MainMethodTest.test(MainTest.java:71)
    at MainTest$MainMethodTest$SwapMethodTest.correct_input_test(MainTest.java:29)

I know where exactly exception comes from. Here is in() method that called from main() to get input from user and exception caught when Scanner tries to read next() from input stream:

public static int in(String message, int from, int to) {
    if (message != null && !message.isEmpty()) {
        System.out.print(message);
    }
    try {
        String input = scanner.next();
        int num;

        /*         this code works correctly for me
                   and is just to check 
                   if user typed correct number 
                   and if the number is in range.
        try {
            if ((num = Integer.parseInt(input)) < from) {
                throw new IllegalArgumentException("\n\nExpected: value >= " + from + "\nActual:   value = " + num + "\n");
            } else if (num > to) {
                throw new IllegalArgumentException("\n\nExpected: value <= " + to + "\nActual:   value = " + num + "\n");
            } else {
                return num;
            }
        } catch (NumberFormatException e) {
            throw new NumberFormatException("\n\nExpected: integer number\nActual:   " + input + "\n");
        }
        */

    } catch (NoSuchElementException e) {
        throw new NoSuchElementException("\n\nArguments input incomplete.");
    }   // at Main.in(Main.java:169) - here is line 169
}

But I don't understand why IS is empty.


Solution

  • Well. Seems I solved it.

    I just moved Scanner initialization from variable declaration to main().

    public class Main {
        private static Scanner scanner = new Scanner(System.in);
    
        public static void main(String[] args) {
            // ....
        }
    }
    

    And now it works for me.

    public class Main {
        private static Scanner scanner;
    
        public static void main(String[] args) {
            scanner = new Scanner(System.in);
            // ....
        }
    }