I faced a similar issue and this is what I ended up doing.
First off, I'd suggest doing as @Jon-Skeet suggests and instead of using the main(String[])
method of the class, create a separate method.
Then you can have that method take in an InputStream
as a parameter and then create a Scanner
object within the method that uses the passed InputStream
as its source. That way you can pass any InputStream
, such as System.in
, to the method when it's called (elaboration below).
package my.package;
import ...;
public class MyClass
{
public static void myMethod(InputStream inputStream)
{
Scanner inputScanner = new Scanner(inputStream);
// Do stuff with the Scanner such as...
String input = inputScanner.nextLine();
System.out.println("You inputted " + input);
}
}
Now, in your production source code you can call myMethod
and pass it System.in
as an argument as such, myMethod(System.in);
And then in your unit tests, you can create mock input values via a ByteArrayInputStream
:
package my.package;
import ...;
public class MyClassTest
{
@Test
void testMyMethod()
{
// Simulates a user inputting the string "Mock input" and hitting enter
assertDoesNotThrow(myMethod(new ByteArrayInputStream("Mock input\n".getBytes())));
}
}
And voila, you now have a way to pass your method mock input as well as it being more modular overall.
I just want to point out without getting too much into it, that when working with System.in
, one needs to be careful about closing it and in unit tests when working with input streams, one needs to be careful about reusing a reference to the same InputStream
as its state can persist across uses.