Could you explain me please how to mock System.in
correctly?
I have a simple test and I assign values to
@Test
public void testBarCorrect() {
System.setIn(new ByteArrayInputStream("7\n2\n3".getBytes()));
someService().consume();
}
Under the hood I do nothing but new Scanner(System.in);
The caveat is that sometimes my test is green but in 80% it throws an Exception "java.util.NoSuchElementException: No line found".
So, if I press run on a test it misbehaves.
Could you give me any hints?
My test:
public class RecordServiceCTLIntegrationTest {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private final PrintStream originalOut = System.out;
private static final InputStream DEFAULT_STDIN = System.in;
@Before
public void setUpStreams() {
System.setOut(new PrintStream(outContent));
System.setIn(DEFAULT_STDIN);
}
@After
public void rollbackChangesToStdin() {
System.setOut(originalOut);
System.setIn(DEFAULT_STDIN);
}
@Test
public void testBarCorrect() {
System.setIn(new ByteArrayInputStream("5\n1\nB\n21\n\n".getBytes()));
buildRecordServiceCTL().run();
assertEquals("Enter Room Id: No active booking for room id: 5\n" +
"Enter Room Id: B:\tBar Fridge\n" +
"R:\tRestaurant\n" +
"S:\tRoom Service\n" +
"Enter service typeEnter cost: Service Bar Fridge has been added for the roomNumber 1\n" +
"Hit <enter> to continuePay for service completed\n", outContent.toString());
}
@Test
public void testRestaurantCorrect() {
System.setIn(new ByteArrayInputStream("5\n1\nR\n21\n\n".getBytes()));
buildRecordServiceCTL().run();
assertEquals("Enter Room Id: No active booking for room id: 5\n" +
"Enter Room Id: B:\tBar Fridge\n" +
"R:\tRestaurant\n" +
"S:\tRoom Service\n" +
"Enter service typeEnter cost: Service Restaurant has been added for the roomNumber 1\n" +
"Hit <enter> to continuePay for service completed\n", outContent.toString());
}
@Test
public void testRoomServiceCorrect() {
System.setIn(new ByteArrayInputStream("5\n1\nS\n21\n\n".getBytes()));
buildRecordServiceCTL().run();
assertEquals("Enter Room Id: No active booking for room id: 5\n" +
"Enter Room Id: B:\tBar Fridge\n" +
"R:\tRestaurant\n" +
"S:\tRoom Service\n" +
"Enter service typeEnter cost: Service Room Service has been added for the roomNumber 1\n" +
"Hit <enter> to continuePay for service completed\n", outContent.toString());
}
@Test(expected = NoSuchElementException.class)
public void testRoomException() {
System.setIn(new ByteArrayInputStream("-1\n\n".getBytes()));
buildRecordServiceCTL().run();
}
@Test
public void cancel() {
System.setIn(new ByteArrayInputStream("5\n1\nS\n20\n".getBytes()));
assertEquals("", outContent.toString());
}
private RecordServiceCTL buildRecordServiceCTL() {
Hotel hotel = new Hotel();
final Guest guest = new Guest("name", "address", 123);
final Room room = new Room(1, SINGLE);
final CreditCard card = new CreditCard(VISA, 123, 123);
final Booking booking = new Booking(guest, room, new Date(), 10, 1, card);
hotel.activeBookingsByRoomId.put(room.getId(), booking);
final Map<Integer,Room> map = new HashMap<>();
map.put(room.getId(), room);
hotel.roomsByType.put(SINGLE, map);
return new RecordServiceCTL(hotel);
}
}
And exception occurs in run method in:
if (stdin == null) {
stdin = new Scanner(System.in);
}
String ans = stdin.nextLine();
This test works fine for me:
@Test
public void testBarCorrect() {
System.setIn(new ByteArrayInputStream("7\n2\n3".getBytes()));
Scanner scanner = new Scanner(System.in);
System.out.println(scanner.nextLine());
System.out.println(scanner.nextLine());
System.out.println(scanner.nextLine());
}
However, if I add the following test, this fails:
@Test
public void testFooCorrect() {
Scanner scanner = new Scanner(System.in);
System.out.println(scanner.nextLine());
System.out.println(scanner.nextLine());
System.out.println(scanner.nextLine());
}
java.util.NoSuchElementException: No line found
at java.util.Scanner.nextLine(Scanner.java:1540)
at com.amazon.adcs.service.dra.domain.A9DeviceTest.testFooCorrect(A9DeviceTest.java:15)
In theory a unit test should leave the JVM in the same state it was before the execution of the test. It's not your case since you have mutated a singleton state, and you don't rollback the modification after the test. If you have multiple tests using System.in
, the first test might be consuming all of it and leaving nothing to the other one.
Normally, I would recommend you to inject an InputStream
in your class and then you're free to do whatever you want without affecting a system constant. Since you tell me that you cannot edit the code, then you should make sure you clean up the state after yourself.
Something like this will do the trick:
private static final InputStream DEFAULT_STDIN = System.in;
@After
public void rollbackChangesToStdin() {
System.setIn(DEFAULT_STDIN);
}
If you have to do this often, you might want to consider implementing a JUnit rule.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With