如何用Mockito模拟Java控制台应用的Scanner?解决Mock失败问题
解决Java控制台应用中Scanner模拟与Mockito Final类报错问题
问题分析
- Mockito报错原因:
java.util.Scanner是final类,默认Mockito不支持mock final类,因此抛出Mockito cannot mock/spy because : final class错误。 - 测试无效原因:待测试类中直接在内部初始化
Scanner,测试代码中mock的Scanner并未注入到目标实例,实际运行时仍使用真实的System.in输入,完全没用到mock对象。
解决方案
第一步:重构待测试类,实现依赖注入
将Scanner通过构造函数注入,让类的依赖可替换,既不影响生产环境使用,又方便测试时传入自定义Scanner实例:
import java.io.InputStreamReader; import java.util.Scanner; // 避免使用Class作为类名,防止与java.lang.Class冲突 public class MyClass { private final Scanner scanner; // 供测试用的构造函数,传入自定义Scanner public MyClass(Scanner scanner) { this.scanner = scanner; } // 生产环境默认构造函数,使用System.in输入 public MyClass() { this(new Scanner(new InputStreamReader(System.in))); } public void startScreen() { System.out.println("Do a"); while (!scanner.hasNext("a")) { System.out.println("No valid option!"); scanner.next(); } String input = scanner.next(); if (input.equals("a")) { System.out.println("a"); } } }
第二步:选择合适的测试方案
方案1:模拟输入流(无需mock,更简洁)
直接用ByteArrayInputStream模拟用户输入"a",让Scanner读取该输入流,完全避免mock final类的问题:
import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; import java.io.InputStreamReader; import java.util.Scanner; class MyClassTest { @Test void testValidInput() { // 模拟用户输入"a",添加换行符匹配Scanner的读取逻辑 String simulatedInput = "a\n"; ByteArrayInputStream inputStream = new ByteArrayInputStream(simulatedInput.getBytes()); Scanner testScanner = new Scanner(new InputStreamReader(inputStream)); MyClass myClass = new MyClass(testScanner); myClass.startScreen(); // 如需验证输出,可重定向System.out: // ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); // System.setOut(new PrintStream(outputStream)); // myClass.startScreen(); // String output = outputStream.toString(); // assert output.contains("Do a") && output.contains("a"); } }
方案2:开启Mockito Final类Mock支持(若偏好使用mock)
如果一定要用Mockito mock Scanner,需开启Mockito对final类的支持:
- 在项目
src/test/resources/mockito-extensions目录下创建文件org.mockito.plugins.MockMaker,内容为:mock-maker-inline - 编写测试代码:
import org.junit.jupiter.api.Test; import org.mockito.Mockito; import java.util.Scanner; class MyClassTest { @Test void testWithMockScanner() { Scanner mockScanner = Mockito.mock(Scanner.class); // 模拟hasNext("a")返回true,next()返回"a" Mockito.when(mockScanner.hasNext("a")).thenReturn(true); Mockito.when(mockScanner.next()).thenReturn("a"); MyClass myClass = new MyClass(mockScanner); myClass.startScreen(); // 验证Scanner的next()方法被调用一次 Mockito.verify(mockScanner, Mockito.times(1)).next(); } }
内容的提问来源于stack exchange,提问作者teres
相关产品推荐
相关产品推荐

