使用Mockito测试Java Future对象中的异常处理

使用mockito测试java future对象中的异常处理

本文详细阐述了如何使用Mockito框架有效测试Java并发编程中Future对象抛出的InterruptedException和ExecutionException。核心在于正确模拟Future.get()方法抛出指定异常,并通过在异常捕获块中引入可验证的业务逻辑(如日志记录),结合Mockito的spy功能,精确验证异常处理逻辑是否被触发并正确执行。

1. Future异常处理测试挑战

在Java并发编程中,Future接口的get()方法会声明抛出InterruptedException和ExecutionException。在编写单元测试时,我们经常需要覆盖这些异常捕获块,以确保程序在异步操作失败时能正确响应。然而,直接模拟Future对象并使其get()方法抛出异常,并验证捕获块是否执行,常常会遇到挑战。

常见的错误尝试是使用when(futureMock.get()).thenThrow(CompletableFuture.completedFuture(interruptedException))。这种写法是错误的,因为thenThrow()期望的是一个Throwable对象(即异常实例),而不是一个Future对象。正确的做法是直接传递异常实例,例如thenThrow(interruptedException)。

更深层次的问题在于,即使成功模拟了异常抛出,如何验证一个仅包含异常处理逻辑(如一个空的catch块)的捕获块是否被执行?单元测试需要可观察的副作用来验证行为。

2. 解决方案核心:可验证的副作用与Mockito Spy

要有效测试Future的异常捕获块,关键在于两点:

立即学习“Java免费学习笔记(深入)”;

正确模拟Future.get()抛出异常。在异常捕获块中引入可验证的副作用。 这通常意味着调用一个外部服务或记录日志。然后,我们可以使用Mockito的spy功能来监视这个副作用是否被触发。

2.1 生产代码设计

为了使异常捕获块可测试,我们需要对其进行改造。假设我们的业务逻辑在捕获到异常时会调用一个服务方法来记录错误。

MyService.java

package de.playground.so74236327;public class MyService {    public void logError(Exception e) {        // 实际的错误记录逻辑,例如写入日志文件或发送告警        System.err.println("Error logged: " + e.getMessage());    }}

ExampleProductionCode.java (核心异常处理逻辑部分)

package de.playground.so74236327;import java.util.ArrayList;import java.util.List;import java.util.concurrent.*;public class ExampleProductionCode {    List<Future> futureData;    EnginesData enginesData;    MyService myService; // 注入MyService    public ExampleProductionCode(MyService myService) {        this.myService = myService;        futureData = new ArrayList();        enginesData = new EnginesData();        // 示例:添加两个真实Future对象,它们内部可能抛出异常        futureData.add(createAndStartAsyncTask());        futureData.add(createAndStartFutureTask());    }    // ... 其他方法,如getFutureData(),getEngineData(),createAndStartAsyncTask(),createAndStartFutureTask() ...    // createAndStartAsyncTask 和 createAndStartFutureTask 内部可以模拟抛出InterruptedException    public void collectAsyncResults() {        futureData.forEach(result -> {            try {                enginesData.add(result.get()); // 尝试获取Future结果            } catch (InterruptedException | ExecutionException e) {                // 关键:在捕获块中调用可验证的服务方法                myService.logError(e);            }        });    }    public class EnginesData {        public void add(Class aclass) {            // 实际的数据处理逻辑        }    }}

在collectAsyncResults()方法中,当result.get()抛出InterruptedException或ExecutionException时,myService.logError(e)方法会被调用。这就是我们测试的切入点。

2.2 测试用例设计

现在,我们可以编写测试用例来验证异常处理逻辑。

FutureExceptionTest.java

package de.playground.so74236327;import org.junit.jupiter.api.Test;import org.junit.jupiter.api.extension.ExtendWith;import org.mockito.Mock;import org.mockito.junit.jupiter.MockitoExtension;import java.util.List;import java.util.concurrent.ExecutionException;import java.util.concurrent.Future;import static org.mockito.Mockito.*;@ExtendWith(MockitoExtension.class) // 启用Mockito JUnit 5扩展public class FutureExceptionTest {    @Mock // 使用@Mock注解创建Future的模拟对象    public Future futureMock;    @Test    public void testInterruptedException() throws ExecutionException, InterruptedException {        // 1. Spy MyService实例        // 我们需要监视MyService的真实行为,以便验证logError是否被调用        MyService mySpy = spy(new MyService());        // 2. 实例化生产代码,并注入spy后的MyService        ExampleProductionCode exampleProductionCode = new ExampleProductionCode(mySpy);        // 获取Future列表,其中包含两个真实Future对象(它们可能内部抛异常)        List<Future> futureData = exampleProductionCode.getFutureData();        // 3. 准备模拟的异常        InterruptedException interruptedException = new InterruptedException("模拟的Interrupted Exception");        // 4. 配置futureMock的get()方法在调用时抛出异常        // 注意:这里直接thenThrow(interruptedException),而不是thenThrow(CompletableFuture.completedFuture(interruptedException))        when(futureMock.get()).thenThrow(interruptedException);        // 5. 将模拟的futureMock添加到futureData列表中        // 这样在collectAsyncResults执行时,会有一个Future对象是模拟的,会抛出我们设定的异常        futureData.add(futureMock);        // 在collectAsyncResults()调用之前,logError应该没有被调用过        verify(mySpy, times(0)).logError(interruptedException);        // 6. 执行生产代码中包含Future.get()调用的方法        exampleProductionCode.collectAsyncResults();        // 7. 验证logError方法是否被调用        // 假设collectAsyncResults()处理了3个Future:        // - 两个来自ExampleProductionCode内部创建的真实Future,它们可能抛出异常。        // - 一个是我们添加的futureMock,它会抛出interruptedException。        // 因此,logError应该被调用3次(如果所有Future都导致异常)。        verify(mySpy, times(3)).logError(any(Exception.class)); // 验证任何Exception类型的调用        // 如果想验证特定异常,可以这样:        // verify(mySpy, times(1)).logError(interruptedException); // 验证模拟的InterruptedException被捕获    }}

3. 关键点解析与注意事项

@Mock与spy的区别@Mock用于创建一个完全模拟的对象,其所有方法默认不执行实际逻辑,需要通过when().thenReturn()或when().thenThrow()进行行为定义。spy(new MyService())用于监视一个真实的对象实例。它会执行对象的真实方法,但我们仍然可以通过doReturn()、doThrow()等方法对其部分行为进行桩化或验证。在本例中,我们希望MyService的logError方法被真实调用,以便我们能验证其调用次数和参数,因此使用spy是合适的。thenThrow()的正确用法: thenThrow()方法期望接收一个Throwable实例。不要传递Future或其他非Throwable对象。可测试性设计: 单元测试的黄金法则之一是“可测试性优先”。如果一个代码块(如空的catch块)没有可观察的副作用,那么它就很难被有效地测试。通过引入myService.logError(e)这样的调用,我们为测试提供了验证点。模拟真实异常场景: 除了模拟Future对象,生产代码中也可以通过CompletableFuture.completeExceptionally(e)或在FutureTask内部的call()/run()方法中抛出异常来模拟真实世界的异常情况。这在ExampleProductionCode的createAndStartAsyncTask和createAndStartFutureTask方法中有所体现。

4. 总结

通过以上方法,我们不仅能够成功模拟Future.get()方法抛出InterruptedException或ExecutionException,还能通过在捕获块中引入可验证的副作用,并结合Mockito的spy和verify功能,精确地测试和验证异常处理逻辑是否被正确触发和执行。这种测试策略确保了异步操作在面临异常时,程序能够按照预期进行错误处理,从而提升了代码的健壮性和可靠性。

以上就是使用Mockito测试Java Future对象中的异常处理的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/136574.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
MySQL数据迁移怎么操作?MySQL跨平台转移的35条高效技巧
上一篇 2025年11月30日 00:26:00
SATA SSD在2024年是否仍具性价比?系统盘与游戏盘实测
下一篇 2025年11月30日 00:28:02

相关推荐

  • Java从文本文件随机读取并打印指定行数内容

    本文旨在指导读者如何使用java程序从文本文件中高效地读取多组固定行数的内容(如诗歌),并随机选择其中一组进行打印。教程将详细介绍如何利用`files.readalllines`、`random`和`list.sublist`等核心api,实现文件的整体读取、随机索引的生成以及特定内容块的提取与输出…

    2026年9月20日
    000
  • mysql如何启用query cache

    MySQL 5.7及之前版本可通过配置启用Query Cache以提升读取性能,首先确认支持性:执行SHOW VARIABLES LIKE ‘have_query_cache’,若返回YES则可继续。接着在my.cnf或my.ini的[mysqld]段添加query_cach…

    2026年9月20日
    200
  • PHP播放HLS视频流的方法_PHP播放HLS视频流方法

    答案:PHP通过权限控制和文件代理实现HLS流安全分发,前端使用HTML5视频标签和hls.js播放。具体描述:HLS将视频切为.ts片段并用.m3u8索引,PHP后端可校验用户权限、防止盗链,动态输出.m3u8或.ts内容;前端通过video标签加载stream.php?id=1,结合hls.js…

    2026年9月20日
    000
  • Java类的初始化顺序是怎样的 静态代码块和构造代码块先后

    Java类初始化顺序为:父类静态成员→子类静态成员→父类实例成员→父类构造函数→子类实例成员→子类构造函数,静态代码块仅加载时执行一次,构造代码块每次创建对象时执行,且均按书写顺序运行。 Java类的初始化顺序遵循一定的规则,理解这些顺序对掌握对象创建过程非常重要。当一个类被加载并创建实例时,各个代…

    2026年9月20日
    100
  • VSCode有哪些必备的插件?

    EditorConfig for VS Code统一代码风格,2. Prettier自动格式化多语言代码,3. ESLint检查JS/TS错误并集成Prettier,4. GitLens增强Git可视化,5. Path Intellisense补全文件路径,6. 括号高亮提升嵌套识别,7. Auto…

    2026年9月20日
    1000
  • Via浏览器怎么让地址栏显示完整的网址链接_Via浏览器显示完整网址的设置方法

    1、打开Via浏览器设置,进入高级设置中的地址栏选项,开启“显示完整网址”功能;2、在外观设置中关闭简洁模式或极简地址栏,以恢复协议头和路径显示;3、高级用户可借助自定义脚本强制输出完整URL,通过工具箱添加执行脚本实现。 如果您在使用Via浏览器时发现地址栏默认只显示域名而隐藏了完整的网址链接,可…

    2026年9月20日
    000
  • Java从文本文件随机读取多行连续内容的教程

    本教程旨在指导java开发者如何高效地从文本文件中随机读取并打印指定数量(例如5行)的连续内容,尤其适用于处理结构化文本块(如诗歌)。我们将探讨如何避免仅读取文件开头固定行数的局限,通过将文件内容一次性加载到内存并结合随机数生成器来精确选取所需的文本块,从而实现真正的随机性与灵活性。 引言与问题分析…

    2026年9月20日
    200
  • ChatGPT代码会出错吗_AI编程中5个常见错误及解决方法

    AI编程中常见错误包括语法不匹配、逻辑遗漏、API误用、安全漏洞和集成困难,需通过版本明确、测试验证、文档核对、安全扫描和上下文补充等方式解决,结合人工审查与测试才能确保代码质量。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ ChatGP…

    2026年9月20日
    100
  • RBAC(基于角色的权限控制)实现方案

    rbac重要,因为它通过角色管理权限,简化了权限管理,提高了系统安全和管理效率。实现rbac时:1.设计数据库结构,定义用户、角色、权限表及中间表;2.在代码中实现权限检查和角色、权限的动态管理;3.优化性能,防止权限泄露,管理角色膨胀。 在探讨RBAC(基于角色的权限控制)实现方案之前,让我们先来…

    2026年9月20日
    000
  • Android Activity与Fragment通信及视图访问的最佳实践

    本文旨在解决android开发中activity与fragment之间视图访问和数据通信的常见问题,特别是当使用bottom navigation activity模板时。我们将探讨为何不能直接在activity中访问fragment视图,并详细介绍如何利用fragment的生命周期方法(如`onv…

    2026年9月20日
    100
  • Redmi K70电池续航表现解析 Redmi K70充电优化技巧

    Redmi K70续航表现因使用习惯而异,重度使用亮屏5.5至6.5小时,日常通勤可撑一天;120W快充19分钟充满,建议随用随充、开启智能充电保护,并避免边充边玩大型游戏;通过开启自动亮度、降低分辨率至1.5K、限制后台高耗电应用可有效延长续航。 Redmi K70的续航到底怎么样,充电怎么用才最…

    2026年9月20日
    100
  • Java Swing:在类中管理 JFrame 实例的两种策略

    本文探讨在 java swing 应用程序中,如何有效地在不同方法中访问和管理 jframe 实例,避免 this 关键字的限制。我们将介绍两种核心策略:将 jframe 作为类成员变量,或使类直接继承 jframe。同时,强调组件应添加到 jframe 的内容面板,而非直接添加到 jframe。 …

    2026年9月20日
    000
  • Linux怎么查看进程使用的端口号

    答案是使用netstat、ss或lsof命令可查看Linux进程占用的端口。首先推荐ss命令,如ss -tulnp | grep 8080,能快速显示监听端口及对应进程;其次netstat -tulnp | grep 8080用法类似,但速度较慢;lsof -i :8080可精确查看指定端口的进程信…

    2026年9月20日
    000
  • 安全优雅地关闭Tomcat Embedded (无Spring环境)

    本文旨在提供一种在没有Spring框架的情况下,安全优雅地关闭Tomcat Embedded服务器的方法。通过手动管理Servlet生命周期和Tomcat实例,确保资源得到正确释放,避免数据丢失或连接中断,保证服务器的平稳关闭。 在嵌入式Tomcat应用中,优雅地关闭服务器至关重要,尤其是在生产环境…

    2026年9月20日
    100
  • Linux如何卸载软件并清理依赖包

    Linux如何卸载软件并清理依赖包Linux如何卸载软件并清理依赖包Linux如何卸载软件并清理依赖包Linux如何卸载软件并清理依赖包

    卸载软件并清理依赖可释放空间和保持系统整洁。Ubuntu/Debian使用sudo apt purge 软件名彻底卸载,sudo apt autoremove清理依赖;CentOS/RHEL/Fedora使用sudo dnf remove 软件名卸载,sudo dnf autoremove清理依赖;…

    2026年9月20日 用户投稿
    000
  • 如何在Java中定义一个包含参数的方法

    定义Java带参方法需明确访问修饰符、返回类型、方法名及参数列表。例如:public static int add(int a, int b) { return a + b; },调用时传入对应类型参数,如add(5, 3)输出结果8,参数类型必须匹配,否则编译错误。 在Java中定义一个包含参数的…

    2026年9月20日
    200
  • JAXB中动态获取Java对象QName并创建JAXBElement的反射策略

    本文探讨了在jaxb中,当`jaxbintrospector.getelementname`无法获取java对象对应的`qname`时,如何通过反射机制调用`objectfactory`中生成的`create`方法来动态创建`jaxbelement`。该方法避免了大量类型判断,提高了代码的灵活性和可…

    2026年9月20日
    000
  • Mockito中利用自定义ArgumentMatcher实现集合内参数匹配

    mockito并未提供直接的`in()`参数匹配器来判断方法参数是否包含在指定集合中。本文将详细介绍如何利用`intthat`(或`argthat`)结合lambda表达式或自定义匹配器,灵活实现对方法参数是否属于某个集合的条件匹配,从而在测试存根(stubbing)或验证(verification…

    2026年9月20日
    000
  • mysql如何优化初级项目数据库性能

    答案:初级项目数据库性能问题多源于设计和使用不当,优化需从表结构、索引、SQL语句和配置入手。应选用合适数据类型、避免NULL、拆分大字段;为常用查询字段建索引,遵循最左前缀原则,避免函数操作导致索引失效;禁止SELECT *,合理使用LIMIT,减少子查询与循环中执行SQL;开启慢查询日志,使用连…

    2026年9月20日
    000
  • Java中将包含嵌套列表的对象列表扁平化为单一元素列表的转换技巧

    本文探讨了在java中如何将一个包含嵌套列表的对象列表进行转换,使其生成一个新的列表,其中每个对象内部的嵌套列表只包含一个元素。文章详细介绍了三种实现方式:基于java 7及以前版本的传统循环方法、利用java 8至java 15的stream api结合`flatmap`操作,以及java 16及…

    2026年9月20日
    200

发表回复

登录后才能评论
关注微信