JUnit单元测试多用户输入:NoSuchElementException问题排查
解决单元测试中Scanner多次读取输入流抛出NoSuchElementException的问题
问题描述
单元测试一个需要两次用户输入的方法时,抛出NoSuchElementException,提示第二个scanner.nextLine()未检测到元素。测试代码中已用\n分隔两次输入,但无效。
测试代码:
@Test public void testMultipleValidMenuInput(){ ByteArrayInputStream inputStream = new ByteArrayInputStream("TestString\n1".getBytes()); System.setIn(inputStream); sampleAccount.enrolCourse(sampleList); Assert.assertEquals("FirstExpectedLine\nSecondExpectedLine", outputStreamCaptor.toString().trim()); }
目标方法核心代码:
public void enrolCourse(courseList listOfCourses){ // ... Scanner scanVar = new Scanner(System.in); // 第一个Scanner实例 String searchTerm = scanVar.nextLine(); // ... if (applicableCourses.size() > 1) { // 调用menuSelect方法 } } private boolean menuSelect(ArrayList<Course> courseArray, boolean enrolOrWithdraw) throws DuplicateClassException{ Scanner scanVar = new Scanner(System.in); // 第二个Scanner实例 // ... int inputVar = Character.getNumericValue(scanVar.nextLine().charAt(0)); // 抛出异常的位置 // ... }
问题原因
核心问题是重复创建Scanner实例读取同一个System.in输入流:
- 第一个Scanner在
enrolCourse中读取第一行输入后,输入流的指针已移至末尾(输入流仅包含"TestString\n1"两行内容)。 - 第二个Scanner在
menuSelect中尝试读取时,输入流已无剩余内容,调用nextLine()直接抛出NoSuchElementException。
解决方案
方案1:复用同一个Scanner实例
将Scanner作为参数传递给menuSelect方法,避免重复创建:
public void enrolCourse(courseList listOfCourses){ ArrayList<Course> applicableCourses = new ArrayList<Course>(); Scanner scanVar = new Scanner(System.in); // 仅创建一次Scanner String searchTerm = scanVar.nextLine(); boolean termFound = false; for (Course e: listOfCourses.getCourseList()) { if (e.getCourseName().toLowerCase().contains(searchTerm.toLowerCase())){ applicableCourses.add(e); System.out.println(e.getCourseName() + " has been found."); termFound = true; } } if (!termFound) { System.out.printf("Courses containing %s has not been found. %n", searchTerm); } else if (applicableCourses.size() == 1) { // 原有逻辑不变 } else if (applicableCourses.size() > 1) { boolean selectionNotMade = true; do { try{ // 将Scanner作为参数传入menuSelect selectionNotMade = menuSelect(scanVar, applicableCourses, true); } catch (DuplicateClassException e){ System.out.println(e); } } while(selectionNotMade); } } // 修改方法参数,接收外部传入的Scanner private boolean menuSelect(Scanner scanVar, ArrayList<Course> courseArray, boolean enrolOrWithdraw) throws DuplicateClassException{ for(int i = 0; i < courseArray.size(); i++) { System.out.printf("%s ) %s %n", i+1, courseArray.get(i).getCourseName()); } System.out.println("Please select a number according to which course you would like."); try { int inputVar = Character.getNumericValue(scanVar.nextLine().charAt(0)); // 原有逻辑不变 } catch (IndexOutOfBoundsException e) { System.err.println("Selection could not be made."); return true; } }
方案2:将Scanner作为类成员变量
如果类中多个方法都需要读取输入,可将Scanner声明为类成员变量统一管理:
public class YourAccountClass { private Scanner scanVar = new Scanner(System.in); // 类成员Scanner实例 private List<Course> enrolmentList = new ArrayList<>(); public void enrolCourse(courseList listOfCourses){ // 直接使用成员变量scanVar读取输入 String searchTerm = scanVar.nextLine(); // 后续逻辑不变 } private boolean menuSelect(ArrayList<Course> courseArray, boolean enrolOrWithdraw) throws DuplicateClassException{ // 直接使用成员变量scanVar读取输入 int inputVar = Character.getNumericValue(scanVar.nextLine().charAt(0)); // 后续逻辑不变 } }
额外注意事项
- 不要随意关闭Scanner(除非确认不再使用System.in),关闭Scanner会同时关闭底层的System.in输入流,导致后续无法读取。
- 单元测试中若多次涉及System.in,需在测试前后恢复原输入流,避免测试间干扰:
private InputStream originalIn; @BeforeEach void setUp() { originalIn = System.in; } @AfterEach void tearDown() { System.setIn(originalIn); }
内容的提问来源于stack exchange,提问作者Chrom
相关产品推荐
相关产品推荐

