如何模拟System.in?

How to mock System.in?

你能解释一下如何正确模拟 System.in 吗?

我有一个简单的测试,我给

赋值
@Test
public void testBarCorrect() {
    System.setIn(new ByteArrayInputStream("7\n2\n3".getBytes()));
    someService().consume();
}

在幕后我什么都不做 new Scanner(System.in);

需要注意的是,有时我的测试是绿色的,但在 80% 的情况下它会抛出 Exception "java.util.NoSuchElementException: No line found".

因此,如果我在测试中按 运行,它就会出错。

你能给我一些提示吗?

我的测试:

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

}

并且异常发生在运行方法中:

if (stdin == null) {
    stdin = new Scanner(System.in);
}
String ans = stdin.nextLine();

这个测试对我来说很好:

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

但是,如果我添加以下测试,则会失败:

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

理论上,单元测试应该让 JVM 保持测试执行前的状态。这不是你的情况,因为你已经改变了一个单例状态,并且你不会在测试后回滚修改。如果您使用 System.in 进行多项测试,第一个测试可能会消耗掉所有测试而不会给另一个测试留下任何东西。

通常,我会建议您在 class 中注入一个 InputStream,然后您就可以自由地做任何您想做的事,而不会影响系统常量。既然你告诉我你不能编辑代码,那么你应该确保自己清理状态。

像这样的东西就可以了:

private static final InputStream DEFAULT_STDIN = System.in;

@After
public void rollbackChangesToStdin() {
    System.setIn(DEFAULT_STDIN);
} 

如果您必须经常这样做,您可能需要考虑实施 JUnit rule