0

0

JUnit测试中覆盖异常捕获块(Catch Block)的实用策略

DDD

DDD

发布时间:2025-10-21 10:39:40

|

616人浏览过

|

来源于php中文网

原创

JUnit测试中覆盖异常捕获块(Catch Block)的实用策略

本文深入探讨了在junit测试中如何有效覆盖java代码中的异常捕获块(catch block),特别是当异常由内部依赖抛出时。我们将详细解释为何直接模拟服务方法抛出异常的尝试会失败,并提供一个基于mockito模拟内部依赖抛出特定检查型异常的正确方法,以确保异常处理逻辑得到充分测试,并最终抛出预期的自定义运行时异常。

软件开发中,健壮的异常处理是构建可靠系统的关键一环。为了确保我们的异常捕获逻辑能够按预期工作,编写相应的单元测试变得至关重要。然而,测试那些由内部依赖抛出并由当前方法捕获的异常,常常会给开发者带来困扰。本文将详细阐述如何利用Mockito框架,精准地模拟这些场景,从而有效覆盖代码中的异常捕获块。

理解测试异常捕获块的挑战

考虑以下服务方法,它调用一个外部生产者(snsProducer)发送消息,并捕获可能发生的JsonProcessingException,然后将其包装成一个自定义的SnSException抛出:

public class MyService {
    private final SnsProducer snsProducer;

    public MyService(SnsProducer snsProducer) {
        this.snsProducer = snsProducer;
    }

    public void doCreate(String message) {
        try {
            snsProducer.send(message);
        } catch (JsonProcessingException jpe) {
            // 捕获JsonProcessingException并抛出自定义的SnSException
            throw new SnSException("Could not parse Message to publish to SNS", jpe);
        }
    }
}

// SnsProducer 接口示例
public interface SnsProducer {
    void send(String message) throws JsonProcessingException;
}

// 自定义运行时异常 SnSException
public class SnSException extends RuntimeException {
    public SnSException(String message, Throwable cause) {
        super(message, cause);
    }
}

我们的目标是测试当snsProducer.send(message)抛出JsonProcessingException时,doCreate方法是否能正确捕获并抛出SnSException。

常见的错误尝试及原因分析

许多开发者在尝试测试上述场景时,可能会首先想到直接模拟MyService的doCreate方法来抛出异常,如下所示:

@Test
void snsTest_incorrectAttempt1() {
    // 假设service是MyService的实例,这里尝试模拟service的doCreate方法
    // 当doCreate方法被调用时,直接抛出JsonProcessingException
    // 注意:doCreate方法签名中并未声明抛出JsonProcessingException
    when(service.doCreate(anyString())).thenThrow(new JsonProcessingException("Json Processing Error"){});

    // 期望doCreate方法会抛出SnSException
    assertThrows(SnSException.class, () -> service.doCreate(anyString()));
}

错误原因分析: 这种尝试会导致编译错误或运行时异常,例如Checked exception is invalid for this method!。这是因为MyService.doCreate方法的签名并没有声明它会抛出JsonProcessingException(它内部捕获了该异常)。Mockito的when().thenThrow()方法要求模拟抛出的检查型异常必须与被模拟方法的签名兼容。由于doCreate方法本身不抛出JsonProcessingException,所以直接模拟它抛出这个异常是不合法的。

另一种尝试可能是:

SoftGist
SoftGist

SoftGist是一个软件工具目录站,每天为您带来最好、最令人兴奋的软件新产品。

下载
@Test
void snsTest_incorrectAttempt2() {
    // 假设service是MyService的实例,这里尝试模拟service的doCreate方法
    // 这种模拟方式本身是合法的,但它绕过了doCreate方法内部的try-catch逻辑
    when(service.doCreate(anyString())).thenThrow(new SnSException("Exception"));

    // 期望doCreate方法会抛出SnSException
    assertThrows(SnSException.class, () -> service.doCreate(anyString()));
}

错误原因分析: 尽管这种模拟在语法上是合法的(因为SnSException是运行时异常,或者如果doCreate方法声明了SnSException),但它并没有真正测试到doCreate方法内部的try-catch逻辑。这种模拟方式完全跳过了snsProducer.send()的调用,直接让doCreate方法抛出异常。这并不是我们想要测试的场景——我们想测试的是snsProducer抛出异常后,doCreate如何响应。因此,如果你的测试期望的是snsProducer抛出JsonProcessingException后,doCreate方法内部的catch块被执行并抛出SnSException,那么这种模拟方式将无法覆盖该逻辑。

正确的测试策略:模拟内部依赖

正确的做法是模拟MyService所依赖的snsProducer对象,让它在被调用时抛出JsonProcessingException。这样,MyService.doCreate方法内部的try-catch块就会被触发。

以下是使用JUnit 5和Mockito的完整测试示例:

import com.fasterxml.jackson.core.JsonProcessingException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;

// 假设这些是你的实际类和接口
// public interface SnsProducer { void send(String message) throws JsonProcessingException; }
// public class MyService { ... }
// public class SnSException extends RuntimeException { ... }

@ExtendWith(MockitoExtension.class) // 启用Mockito JUnit 5扩展
class MyServiceTest {

    @Mock // 模拟SnsProducer接口
    private SnsProducer snsProducer;

    @InjectMocks // 注入模拟的SnsProducer到MyService实例中
    private MyService myService;

    // 可以选择在每个测试前初始化,但@Mock和@InjectMocks通常会自动处理
    // @BeforeEach
    // void setUp() {
    //     // 如果不使用@ExtendWith(MockitoExtension.class),则需要手动初始化
    //     // MockitoAnnotations.openMocks(this);
    //     // myService = new MyService(snsProducer); // 如果没有@InjectMocks
    // }

    @Test
    void doCreate_shouldThrowSnSException_whenJsonProcessingExceptionOccurs() throws JsonProcessingException {
        String testMessage = "{\"key\":\"value\"}";

        // 1. 模拟内部依赖 snsProducer 的行为
        // 当 snsProducer.send(testMessage) 被调用时,抛出 JsonProcessingException
        // 注意:这里使用的是 doThrow().when() 语法,因为它更适合模拟 void 方法抛出检查型异常
        doThrow(new JsonProcessingException("Simulated JSON processing error") {})
            .when(snsProducer).send(testMessage);

        // 2. 调用被测试的服务方法
        // 期望 myService.doCreate(testMessage) 会抛出 SnSException
        assertThrows(SnSException.class, () -> myService.doCreate(testMessage));

        // 3. 验证 snsProducer.send 方法确实被调用了一次
        verify(snsProducer, times(1)).send(testMessage);
    }

    @Test
    void doCreate_shouldCallSnsProducerSend_whenMessageIsValid() throws JsonProcessingException {
        String testMessage = "{\"key\":\"value\"}";

        // 1. 模拟 snsProducer 的正常行为(不抛出异常)
        doNothing().when(snsProducer).send(testMessage);

        // 2. 调用服务方法
        myService.doCreate(testMessage);

        // 3. 验证 snsProducer.send 方法确实被调用了一次
        verify(snsProducer, times(1)).send(testMessage);
    }
}

代码解释:

  1. @Mock private SnsProducer snsProducer;: 声明一个SnsProducer的模拟对象。
  2. @InjectMocks private MyService myService;: 创建MyService的一个实例,并将所有被@Mock标记的依赖(这里是snsProducer)自动注入到myService中。
  3. doThrow(new JsonProcessingException("Simulated JSON processing error") {}).when(snsProducer).send(testMessage);: 这是核心步骤。我们指示Mockito,当snsProducer对象的send方法被调用并传入testMessage时,它应该抛出一个JsonProcessingException。
    • JsonProcessingException("Simulated JSON processing error") {}: 创建一个匿名内部类实例,以避免直接实例化抽象类。
    • doThrow().when(): 这种语法常用于模拟void方法抛出异常,或者当when().thenThrow()语法因为类型擦除或其他原因导致问题时。对于非void方法,when(snsProducer.send(testMessage)).thenThrow(...) 同样有效。
  4. assertThrows(SnSException.class, () -> myService.doCreate(testMessage));: 这是JUnit 5提供的断言方法,用于验证在执行lambda表达式myService.doCreate(testMessage)时,是否会抛出SnSException类型的异常。
  5. verify(snsProducer, times(1)).send(testMessage);: 这是一个可选但推荐的步骤,用于验证snsProducer.send方法确实被调用了一次。这有助于确认测试路径是正确的,并且服务方法确实尝试了与依赖进行交互。

总结与最佳实践

  • 模拟异常的来源: 始终模拟抛出异常的实际来源(即内部依赖),而不是被测试的服务方法本身。
  • 理解方法签名: 在使用when().thenThrow()时,要确保模拟抛出的检查型异常与被模拟方法的签名兼容。对于void方法或需要更灵活控制的场景,doThrow().when()通常是更好的选择。
  • 使用assertThrows: JUnit 5的assertThrows方法是验证异常抛出的标准和推荐方式,它简洁且易读。
  • 验证交互: 使用Mockito.verify()来验证模拟对象的方法是否被按预期调用,这能增强测试的健壮性。
  • 覆盖所有路径: 除了异常路径,也要确保测试正常执行路径,以确保服务方法在没有异常发生时也能正确工作。

通过遵循这些策略,您可以有效地为Java代码中的异常捕获块编写单元测试,从而提高代码的质量和可靠性。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

419

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

535

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

311

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

77

2025.09.10

软件测试常用工具
软件测试常用工具

软件测试常用工具有Selenium、JUnit、Appium、JMeter、LoadRunner、Postman、TestNG、LoadUI、SoapUI、Cucumber和Robot Framework等等。测试人员可以根据具体的测试需求和技术栈选择适合的工具,提高测试效率和准确性 。

439

2023.10.13

java测试工具有哪些
java测试工具有哪些

java测试工具有JUnit、TestNG、Mockito、Selenium、Apache JMeter和Cucumber。php还给大家带来了java有关的教程,欢迎大家前来学习阅读,希望对大家能有所帮助。

300

2023.10.23

Java 单元测试
Java 单元测试

本专题聚焦 Java 在软件测试与持续集成流程中的实战应用,系统讲解 JUnit 单元测试框架、Mock 数据、集成测试、代码覆盖率分析、Maven 测试配置、CI/CD 流水线搭建(Jenkins、GitHub Actions)等关键内容。通过实战案例(如企业级项目自动化测试、持续交付流程搭建),帮助学习者掌握 Java 项目质量保障与自动化交付的完整体系。

19

2025.10.24

scripterror怎么解决
scripterror怎么解决

scripterror的解决办法有检查语法、文件路径、检查网络连接、浏览器兼容性、使用try-catch语句、使用开发者工具进行调试、更新浏览器和JavaScript库或寻求专业帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

208

2023.10.18

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

158

2026.01.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 3万人学习

C# 教程
C# 教程

共94课时 | 7.8万人学习

Java 教程
Java 教程

共578课时 | 52.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号