JUnit: How to simulate System.in testing?

JavaCommand LineJunit

Java Problem Overview


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

Java Solutions


Solution 1 - Java

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);
}

Solution 2 - Java

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.

Solution 3 - Java

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.

Solution 4 - Java

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.

Solution 5 - Java

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.

Solution 6 - Java

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.

Solution 7 - Java

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.

Solution 8 - Java

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);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNoppanitView Question on Stackoverflow
Solution 1 - JavaMcDowellView Answer on Stackoverflow
Solution 2 - JavaAntônio MedeirosView Answer on Stackoverflow
Solution 3 - JavaYishaiView Answer on Stackoverflow
Solution 4 - JavaAsaphView Answer on Stackoverflow
Solution 5 - JavaStefan BirknerView Answer on Stackoverflow
Solution 6 - JavaOscarRyzView Answer on Stackoverflow
Solution 7 - Javamike rodentView Answer on Stackoverflow
Solution 8 - JavaShimon DoodkinView Answer on Stackoverflow