如何Mock Scanner与File类?测试sendMailWitchChanges方法时出现空指针异常的解决方案
解决Mockito模拟Scanner构造函数时的NullPointerException问题
我太懂你这个坑了!你遇到的NullPointerException是因为默认的Mockito核心库不支持直接模拟构造函数调用——也就是你写的when(new Scanner(any(File.class))).thenReturn(scannerMock)这行,Mockito没法直接拦截new Scanner()的构造行为,所以才会抛出空指针。
问题根源
Mockito默认只能模拟对象的方法调用,要拦截并模拟构造函数,需要用到mockito-inline扩展(Mockito 3.4+版本开始支持),它专门用来处理构造函数、静态方法这类原本Mockito核心不支持的场景。
正确的测试代码写法
首先,确保你的项目依赖里引入了mockito-inline:
- 如果是Maven,在pom.xml里添加:
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>4.11.0</version> <!-- 用最新稳定版即可 --> <scope>test</scope> </dependency>
- 如果是Gradle,在build.gradle里添加:
testImplementation 'org.mockito:mockito-inline:4.11.0'
然后修改你的测试代码,用Mockito.mockConstruction()来模拟Scanner的构造过程:
@Test void sendMailWitchChanges() throws MessagingException, IOException { // 用mockConstruction模拟Scanner的构造,捕获创建的实例并设置行为 try (MockedConstruction<Scanner> mockedScanner = Mockito.mockConstruction(Scanner.class, (mock, context) -> { // 处理Scanner的链式调用:useDelimiter返回自身 when(mock.useDelimiter(anyString())).thenReturn(mock); when(mock.next()).thenReturn("afterChanges"); })) { LocalDateTime now = LocalDateTime.now(); UserEntity user = new UserEntity(); JobEntity job = new JobEntity("uuid", now, 1, Set.of(user)); DirectoryEntity directory = new DirectoryEntity(1, "directory"); FileIdFilePathFileContentEntity fileEntity = new FileIdFilePathFileContentEntity(1, "fileName", "fileContent"); mailService.sendMailWitchChanges(job, directory, fileEntity, now, "fileName"); // 验证目标方法被调用一次 Mockito.verify(mailService, times(1)) .sendMailWitchChanges(job, directory, fileEntity, now, "fileName"); // 可选:验证Scanner的构造和方法调用是否符合预期 List<Scanner> constructedScanners = mockedScanner.constructed(); assertThat(constructedScanners).hasSize(1); verify(constructedScanners.get(0)).useDelimiter("\\Z"); verify(constructedScanners.get(0)).next(); } }
关键修改点解释
MockedConstruction:这是mockito-inline提供的API,用来拦截指定类的构造函数调用,所有new Scanner(...)都会返回我们的mock实例。- 资源管理:用try-with-resources包裹,确保mock构造的资源在测试结束后自动清理,避免影响其他测试用例。
- 链式调用处理:Scanner的
useDelimiter()返回自身,所以必须设置when(mock.useDelimiter(anyString())).thenReturn(mock),否则链式调用scanner.useDelimiter(...).next()会抛出空指针。 - 额外验证(可选):通过
mockedScanner.constructed()获取所有构造的mock实例,验证方法调用细节,让测试逻辑更严谨。
另外,你原来代码里verificationData -> times(1)的写法没必要,直接用times(1)就可以,我已经帮你修正啦。
内容的提问来源于stack exchange,提问作者Emanuelo
相关产品推荐
相关产品推荐

