59

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

informatik01
  • 15,174
  • 9
  • 67
  • 100
Noppanit
  • 593
  • 1
  • 4
  • 4

8 Answers8

67

It is technically possible to switch System.in, but in general, it would be more robust not to call it directly in your code, but add a layer of indirection so the input source is controlled from one point in your application. Exactly how you do that is an implementation detail - the suggestions of dependency injection are fine, but you don't necessarily need to introduce 3rd party frameworks; you could pass round an I/O context from the calling code, for example.

How to switch System.in:

String data = "Hello, World!\r\n";
InputStream stdin = System.in;
try {
  System.setIn(new ByteArrayInputStream(data.getBytes()));
  Scanner scanner = new Scanner(System.in);
  System.out.println(scanner.nextLine());
} finally {
  System.setIn(stdin);
}
McDowell
  • 102,869
  • 29
  • 193
  • 261
21

Based on @McDowell's answer and another answer that shows how to test System.out, I would like to share my solution to give an input to a program and test its output.

As a reference, I use JUnit 4.12.

Let's say we have this program that simply replicates input to output:

import java.util.Scanner;

public class SimpleProgram {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print(scanner.next());
        scanner.close();
    }
}

To test it, we can use the following class:

import static org.junit.Assert.*;

import java.io.*;

import org.junit.*;

public class SimpleProgramTest {
    private final InputStream systemIn = System.in;
    private final PrintStream systemOut = System.out;

    private ByteArrayInputStream testIn;
    private ByteArrayOutputStream testOut;

    @Before
    public void setUpOutput() {
        testOut = new ByteArrayOutputStream();
        System.setOut(new PrintStream(testOut));
    }

    private void provideInput(String data) {
        testIn = new ByteArrayInputStream(data.getBytes());
        System.setIn(testIn);
    }

    private String getOutput() {
        return testOut.toString();
    }

    @After
    public void restoreSystemInputOutput() {
        System.setIn(systemIn);
        System.setOut(systemOut);
    }

    @Test
    public void testCase1() {
        final String testString = "Hello!";
        provideInput(testString);

        SimpleProgram.main(new String[0]);

        assertEquals(testString, getOutput());
    }
}

I won't explain much, because I believe the code is readable and I cited my sources.

When JUnit runs testCase1(), it is going to call the helper methods in the order they appear:

  1. setUpOutput(), because of the @Before annotation
  2. provideInput(String data), called from testCase1()
  3. getOutput(), called from testCase1()
  4. restoreSystemInputOutput(), because of the @After annotation

I didn't test System.err because I didn't need it, but it should be easy to implement, similar to testing System.out.

Antônio Medeiros
  • 2,278
  • 20
  • 17
  • 3
    How would you continue with multiple inputs? – Moustachio Jul 04 '18 at 22:28
  • 2
    For what I needed, it was enough to pass a `String` with newline characters to `provideInput(String data)`. To my program, which runs on a command line interface, each time the user enters some text and hits `Enter`, that text is treated as a new input. Please note `SimpleProgram` uses the Java SE [`Scanner`](https://docs.oracle.com/javase/6/docs/api/java/util/Scanner.html) class to read user's input. – Antônio Medeiros Jul 05 '18 at 17:27
  • 3
    So, I could have called `provideInput("Input 1\nInput 2\nInput 3");` instead. – Antônio Medeiros Jul 05 '18 at 17:30
8

There are a few ways to approach this. The most complete way is to pass in an InputStream while running the class under test which is a fake InputStream which passes simulated data to your class. You can look at a dependency injection framework (such as Google Guice) if you need to do this a lot in your code, but the simple way is:

 public class MyClass {
     private InputStream systemIn;

     public MyClass() {
         this(System.in);
     }

     public MyClass(InputStream in) {
         systemIn = in;
     }
 }

Under test you would call the constructor that takes the input stream. You cloud even make that constructor package private and put the test in the same package, so that other code would not generally consider using it.

Yishai
  • 84,976
  • 26
  • 176
  • 250
  • 3
    +1. I'm with you on this. I would go a step further: `InputData` as a high level wrapper around `InputStream` in unit testing, you should care more about what your class does, and not really about the integration. – OscarRyz Oct 30 '09 at 05:33
  • 1
    Instead of creating the variable `systemIn` you could use the method `System.setIn(in)`. So you can call `System.in` normally but with the updated version. – Laraconda Mar 22 '17 at 03:37
6

Try to refactor your code to use dependency injection. Instead of having your a method that uses System.in directly, have the method accept an InputStream as an argument. Then in your junit test, you'll be able to pass a test InputStream implementation in place of System.in.

Asaph
  • 147,774
  • 24
  • 184
  • 187
4

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.

Stefan Birkner
  • 21,766
  • 10
  • 52
  • 68
  • I just tried using your library, which seemed very good... but then I tried using it with Rule public MockitoRule rule = MockitoJUnit.rule();, and it seemed, as far as I tried, that the two couldn't be combined... So I wasn't able to combine it with injecting mocks... – mike rodent Jan 22 '17 at 18:59
  • That should not be the case. Could you please create an issue for the System Rules library and provide your failing example. – Stefan Birkner Jan 23 '17 at 22:10
  • Thanks... now I know it's meant to work I'll look into it and create an issue if applicable. It seemed to be the `TextFromStandardInputStream` which I had a problem with, but the `SystemOutRule` worked OK. – mike rodent Jan 24 '17 at 07:59
  • This is a great library but be aware it isn't compatible with junit5 . I had to go back to junit4 to get it to work. It's a known bug see the git repo linked in stefan's comment – Daniel Jan 19 '18 at 15:23
1

You could create a custom InputStream and attach it to the System class

class FakeInputStream extends InputStream {

    public int read() {
         return -1;
    }
}

And then use it with your Scanner

System.in = new FakeInputStream();

Before:

InputStream in = System.in;
...
Scanner scanner = new Scanner( in );

After:

InputStream in = new FakeInputStream();
...
Scanner scanner = new Scanner( in );

Although I think you should better to test how your class should work with the data read from the input stream and not really how it reads from there.

OscarRyz
  • 184,433
  • 106
  • 369
  • 548
  • From a TDD perspective, this avoids the design that the test is "driving" or attempting to indicate. However, the OP didn't specify TDD, and from a test-after perspective, it is a very reasonable thing to do - take advantage of the system global. – Yishai Oct 30 '09 at 03:53
  • You can't just do System.in = xxx as System.in is final. You can use System.setIn, but make sure you return to the default in the tear down. Also you don't need to roll your own InputStream, ByteArrayInputStream will do the job nicely. – Michael Lloyd Lee mlk Oct 30 '09 at 10:14
  • Oohh yeap, I got confused. What I tried to say was ... well I will edit my entry :) – OscarRyz Oct 31 '09 at 03:45
1

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.

mike rodent
  • 10,479
  • 10
  • 80
  • 104
0

maybe like this (not tested):

InputStream save_in=System.in;final PipedOutputStream in = new PipedOutputStream(); System.setIn(new PipedInputStream(in));

in.write("text".getBytes("utf-8"));

System.setIn( save_in );

more parts:

//PrintStream save_out=System.out;final ByteArrayOutputStream out = new ByteArrayOutputStream();System.setOut(new PrintStream(out));

InputStream save_in=System.in;final PipedOutputStream in = new PipedOutputStream(); System.setIn(new PipedInputStream(in));

//start something that reads stdin probably in a new thread
//  Thread thread=new Thread(new Runnable() {
//      @Override
//      public void run() {
//          CoursesApiApp.main(new String[]{});                 
//      }
//  });
//  thread.start();


//maybe wait or read the output
//  for(int limit=0; limit<60 && not_ready ; limit++)
//  {
//      try {
//          Thread.sleep(100);
//      } catch (InterruptedException e) {
//          e.printStackTrace();
//      }
//  }


in.write("text".getBytes("utf-8"));

System.setIn( save_in );

//System.setOut(save_out);
Shimon Doodkin
  • 3,676
  • 29
  • 33