Testing JUnit using simulated user input

I am trying to create some JUnit tests for a method that requires user input. The tested method looks somewhat as follows:

public static int testUserInput() { Scanner keyboard = new Scanner(System.in); System.out.println("Give a number between 1 and 10"); int input = keyboard.nextInt(); while (input < 1 || input > 10) { System.out.println("Wrong number, try again."); input = keyboard.nextInt(); } return input; } 

Is there a way to automatically pass an int program instead of me or someone else doing it manually in the JUnit testing method? How to simulate user input?

Thanks in advance.

+50
java eclipse junit user-input
Jun 20 '11 at 18:28
source share
7 answers

You can replace System.in with your own stream by calling System.setIn (InputStream in) . The input stream can be an array of bytes:

 ByteArrayInputStream in = new ByteArrayInputStream("My string".getBytes()); System.setIn(in); // do your thing // optionally, reset System.in to its original System.setIn(System.in) 

Different approaches can make this method more reliable by passing IN and OUT as parameters:

 public static int testUserInput(InputStream in,PrintStream out) { Scanner keyboard = new Scanner(in); out.println("Give a number between 1 and 10"); int input = keyboard.nextInt(); while (input < 1 || input > 10) { out.println("Wrong number, try again."); input = keyboard.nextInt(); } return input; } 
+72
Jun 20 '11 at 19:11
source share

To test your code, you must create a wrapper for system I / O functions. You can do this with dependency injection by providing us with a class that can request new integers:

 public static class IntegerAsker { private final Scanner scanner; private final PrintStream out; public IntegerAsker(InputStream in, PrintStream out) { scanner = new Scanner(in); this.out = out; } public int ask(String message) { out.println(message); return scanner.nextInt(); } } 

Then you can create tests for your function using the framework (I use Mockito):

 @Test public void getsIntegerWhenWithinBoundsOfOneToTen() throws Exception { IntegerAsker asker = mock(IntegerAsker.class); when(asker.ask(anyString())).thenReturn(3); assertEquals(getBoundIntegerFromUser(asker), 3); } @Test public void asksForNewIntegerWhenOutsideBoundsOfOneToTen() throws Exception { IntegerAsker asker = mock(IntegerAsker.class); when(asker.ask("Give a number between 1 and 10")).thenReturn(99); when(asker.ask("Wrong number, try again.")).thenReturn(3); getBoundIntegerFromUser(asker); verify(asker).ask("Wrong number, try again."); } 

Then write your function that passes the tests. The function is much cleaner as you can remove the request / receive integer duplication and the actual system calls are encapsulated.

 public static void main(String[] args) { getBoundIntegerFromUser(new IntegerAsker(System.in, System.out)); } public static int getBoundIntegerFromUser(IntegerAsker asker) { int input = asker.ask("Give a number between 1 and 10"); while (input < 1 || input > 10) input = asker.ask("Wrong number, try again."); return input; } 

This may seem redundant for your small example, but if you are creating a larger application developing in this way, it can be quite fast.

+15
Jun 20 '11 at 19:46
source share

One common way to test similar code would be to extract a method that uses a scanner and PrintWriter similar to this StackOverflow answer , and verify that:

 public void processUserInput() { processUserInput(new Scanner(System.in), System.out); } /** For testing. Package-private if possible. */ public void processUserInput(Scanner scanner, PrintWriter output) { output.println("Give a number between 1 and 10"); int input = scanner.nextInt(); while (input < 1 || input > 10) { output.println("Wrong number, try again."); input = scanner.nextInt(); } return input; } 

Please note that you will not be able to read your output to the end, and you will need to indicate all your data in front:

 @Test public void shouldProcessUserInput() { StringWriter output = new StringWriter(); String input = "11\n" // "Wrong number, try again." + "10\n"; assertEquals(10, systemUnderTest.processUserInput( new Scanner(input), new PrintWriter(output))); assertThat(output.toString(), contains("Wrong number, try again."));); } 

Of course, instead of creating an overload method, you can also save the “scanner” and “output” as mutable fields in the system under test. As a rule, I prefer to keep classes as stateless as possible, but this is not a very big concession if it is important for you or your staff / instructors.

You can also put your test code in the same Java package as the test code (even if it is in a different source folder), which allows you to reduce the visibility of two parameter overloads that must be private to the package.

+5
Apr 29 '14 at 3:11
source share

You can start by extracting logic that returns the number from the keyboard into your own method. Then you can check the validation logic without worrying about the keyboard. To test the keyboard.nextInt () call, you might consider using a mock object.

+1
Jun 20 2018-11-18T00:
source share

I found it useful to create an interface that defines methods similar to java.io.Console, and then use it to read or write to System.out. The actual implementation will delegate System.console (), while your version of JUnit may be a mock object with completed inputs and expected answers.

For example, you should create a MockConsole containing a canned user login. The breadboard implementation will call the input line from the list every time readLine is called. It will also collect all the output recorded in the answer list. At the end of the test, if all goes well, then all of your data will be read, and you can claim the output.

+1
Jun 20 '11 at 19:29
source share

I managed to find an easier way. However, you should use the System.rules external library . Author @Stefan Birkner

I just took the example provided here, I think it could not become simpler :)

 import java.util.Scanner; public class Summarize { public static int sumOfNumbersFromSystemIn() { Scanner scanner = new Scanner(System.in); int firstSummand = scanner.nextInt(); int secondSummand = scanner.nextInt(); return firstSummand + secondSummand; } } Test import static org.junit.Assert.*; import static org.junit.contrib.java.lang.system.TextFromStandardInputStream.*; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.TextFromStandardInputStream; public class SummarizeTest { @Rule public final TextFromStandardInputStream systemInMock = emptyStandardInputStream(); @Test public void summarizesTwoNumbers() { systemInMock.provideLines("1", "2"); assertEquals(3, Summarize.sumOfNumbersFromSystemIn()); } } 

The problem, however, in my case, my second input has spaces, and this makes the entire input stream null!

+1
Nov 15 '16 at 20:16
source share

I fixed the reading problem from stdin to mimic the console ...

My problems were that I would try to write a console in JUnit test to create a specific object ...

The problem is what you say: how can I write in Stdin from a JUnit test?

Then in college I will learn about redirects, as you say that System.setIn (InputStream) changes the stdin filedescriptor, and you can write then ...

But there is one more possibility to fix ... the JUnit test block expects to read from your new InputStream, so you need to create a stream to read from the InputStream and from the JUnit Thread test write in the new Stdin ... First you have to write to Stdin, because, if you later write about creating a Thread to read from stdin, you will probably have race conditions ... you can write to InputStream earlier to read, or you can read from InputStream before writing ...

This is my code, I have a bad English skill. Hopefully all you can understand is the problem and the solution to simulate a stdin entry from a JUnit test.

 private void readFromConsole(String data) throws InterruptedException { System.setIn(new ByteArrayInputStream(data.getBytes())); Thread rC = new Thread() { @Override public void run() { study = new Study(); study.read(System.in); } }; rC.start(); rC.join(); } 
+1
Mar 26 '17 at 18:47
source share



All Articles