97

I am trying to create some JUnit tests for a method that requires user input. The method under test looks somewhat like the following method:

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 possible way to automatically pass the program an int instead of me or someone else doing this manually in the JUnit test method? Like simulating the user input?

starball
  • 20,030
  • 7
  • 43
  • 238
Wimpey
  • 1,081
  • 1
  • 8
  • 5
  • 7
    What are you testing, exactly? Scanner? A test method should typically assert something to be useful. – Markus Jun 20 '11 at 18:33
  • 2
    You shouldn't have to test Java's Scanner class. You could manually set your input and simply test your own logic. int input = -1 or 5 or 11 will cover your logic – blong824 Jun 20 '11 at 18:36
  • 3
    Six years later... and it's still a good question. Not least because when starting to develop an app you may typically not want to have all the bells and whistles of JavaFX, but instead just start using the humble command line for a bit of very basic interaction. Shame that JUnit doesn't make this quite a bit easier. For me Omar Elshal's answer is very nice, with minimal "contrived" or "distorted" app coding involved... – mike rodent Jun 04 '17 at 17:34

7 Answers7

120

You can replace System.in with you own stream by calling System.setIn(InputStream in). InputStream can be a byte array:

InputStream sysInBackup = System.in; // backup System.in to restore it later
ByteArrayInputStream in = new ByteArrayInputStream("My string".getBytes());
System.setIn(in);

// do your thing

// optionally, reset System.in to its original
System.setIn(sysInBackup);

Different approach can be make this method more testable 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;
}
Tom
  • 16,842
  • 17
  • 45
  • 54
KrzyH
  • 4,256
  • 1
  • 31
  • 43
  • 7
    @KrzyH how would I go about doing this with multiple input? Say that in //do your thing I have three prompts for user input. How would I go about that? – Stupid.Fat.Cat May 14 '14 at 11:44
  • 1
    @Stupid.Fat.Cat I have added second approach to problem, more elegant and felxible – KrzyH May 14 '14 at 12:38
  • 1
    @KrzyH For the second approach, you will be required to pass in the input stream and the output stream as a parameter in the method definition, which is what I am not looking for. Do you know of any better way? – Chirag Oct 01 '15 at 09:42
  • 6
    How do I simulate pressing the enter key? Right now, the program just reads all the input in one shot, which is no good. I tried `\n` but that doesn't make a difference – CodyBugstein Oct 19 '15 at 20:50
  • @Imray: Have you solved that issue? I have the same here. – t777 Dec 10 '15 at 23:39
  • @t777 nope I have not :( – CodyBugstein Dec 11 '15 at 00:13
  • 5
    @CodyBugstein Use `ByteArrayInputStream in = new ByteArrayInputStream(("1" + System.lineSeparator() + "2").getBytes());` to get multiple inputs for different `keyboard.nextLine()` calls. – A Jar of Clay Apr 02 '19 at 10:03
  • That didn't work for me @AJarofClay - I needed to move the initialization of Scanner after setIn'ing the text responses. See my answer here: https://stackoverflow.com/a/75735119/6994979 – Adam D. Mar 14 '23 at 15:41
20

To test drive your code, you should create a wrapper for system input/output functions. You can do this using dependency injection, giving us a class that can ask for 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 a mock 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 since you can remove the asking/getting 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 like overkill for your small example, but if you are building a larger application developing like this can payoff rather quickly.

CSchulz
  • 10,882
  • 11
  • 60
  • 114
Garrett Hall
  • 29,524
  • 10
  • 61
  • 76
6

One common way to test similar code would be to extract a method that takes in a Scanner and a PrintWriter, similar to this StackOverflow answer, and test 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;
}

Do note that you won't be able to read your output until the end, and you'll have to specify all of your input up 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, rather than creating an overload method, you could also keep the "scanner" and "output" as mutable fields in your system under test. I tend to like keeping classes as stateless as possible, but that's not a very big concession if it matters to you or your coworkers/instructor.

You might also choose to put your test code in the same Java package as the code under test (even if it's in a different source folder), which allows you to relax the visibility of the two parameter overload to be package-private.

Community
  • 1
  • 1
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • You meant for testing, the method processUserInput() invokes method(in, out) and not processUserInput(in, out) ? – NadZ Dec 21 '16 at 07:43
  • @NadZ Of course; I started with a general example and incompletely changed it back to being specific to the question. – Jeff Bowman Dec 21 '16 at 11:24
4

I managed to find a simpler way. However, you have to use external library System.rules by @Stefan Birkner

I just took the example provided there, I think it couldn't have gotten more 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 whole input stream null!

Tom
  • 16,842
  • 17
  • 45
  • 54
  • This works very well... I don't know what you mean by "input stream null". What's good is that `inputEntry = scanner.nextLine();` when used with `System.in` will always wait for the user (and will accept an empty line as an empty `String`)... whereas when you supply lines using `systemInMock.provideLines()` this will throw `NoSuchElementException` when the lines run out. This really makes it easy not to "distort" the app code too much to cater for the testing. – mike rodent Jun 01 '17 at 12:52
  • I don't remember exactly what was the problem, but you are right, I checked my code again and noticed that I fixed it in two ways: 1. Using systemInMock: `systemInMock.provideLines("1", "2");` 2. Using System.setIn without external libraries: `String data2 = "1 2";` `System.setIn(new ByteArrayInputStream(data2.getBytes()));` – Omar Elshal Jun 02 '17 at 15:29
3

I have fixed the problem about read from stdin to simulate a console...

My problems was I'd like try write in JUnit test the console to create a certain object...

The problem is like all you say : How Can I write in the Stdin from JUnit test?

Then at college I learn about redirections like you say System.setIn(InputStream) change the stdin filedescriptor and you can write in then...

But there is one more proble to fix... the JUnit test block waiting read from your new InputStream, so you need create a thread to read from the InputStream and from JUnit test Thread write in the new Stdin... First you have to write in the Stdin because if you write later of create the Thread to read from stdin you likely will have race Conditions... you can write in the InputStream before to read or you can read from InputStream before write...

This is my code, my english skill is bad I hope all you can understand the problem and the solution to simulate write in stdin from 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();      
}
3

You might start by extracting out the logic that retrieves the number from the keyboard into its own method. Then you can test the validation logic without worrying about the keyboard. In order to test the keyboard.nextInt() call you may want to consider using a mock object.

Sarah Haskins
  • 1,355
  • 2
  • 16
  • 25
1

I've found it helpful to create an interface that defines methods similar to java.io.Console and then use that for reading or writing to the System.out. The real implementation will delegate to System.console() while your JUnit version can be a mock object with canned input and expected responses.

For example, you'd construct a MockConsole that contained the canned input from the user. The mock implementation would pop an input string off the list each time readLine was called. It would also gather all of the output written to a list of responses. At the end of the test, if all went well, then all of your input would have been read and you can assert on the output.

massfords
  • 689
  • 7
  • 12