You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为CharSource.read()模拟IOException?解决测试空指针异常

问题排查:Mockito模拟Resources静态方法抛出NPE而非预期UncheckedIOException

问题场景

类X的getInputString方法通过Guava的Resources工具类读取资源,捕获IOException后转抛UncheckedIOException。但用Mockito模拟Resources静态方法时,测试抛出了NullPointerException,而非预期的UncheckedIOException。

待测试代码

public class X {
    protected String getInputString(final String inputPath) {
        try {
            return Resources.asCharSource(Resources.getResource(inputPath), UTF_8).read();
        } catch (final IOException e) {
            log.error("Error loading partner aliases from local config", e);
            throw new UncheckedIOException(e);
        }
    }
}

测试代码

@Test
public void Failed() throws Exception{
    URL url = Resources.getResource("resources/linearPartners.json");
    CharSource s;
    try{
        s = new CharSource() {
            @Override
            public Reader openStream() throws IOException {
                throw new IOException("Expected as a test");
            }

            @Override
            public String read() throws IOException {
                throw new IOException("Expected as a test");
            }
        };
        try (MockedStatic<Resources> resources = Mockito.mockStatic(Resources.class)) {
            resources.when(() -> Resources.asCharSource(url, UTF_8))
                .thenReturn(s);
            Assertions.assertThrows(UncheckedIOException.class, () -> staticConfigPartnerAliasesPersistenceFacade.getInputString("resources/file.json"));
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

错误信息

org.opentest4j.AssertionFailedError: Unexpected exception type thrown ==> expected: <java.io.UncheckedIOException> but was: <java.lang.NullPointerException>
     [java]        org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:65)
     [java]        org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:37)
     [java]        org.junit.jupiter.api.Assertions.assertThrows(Assertions.java:3082)

原因分析

  1. 未模拟Resources.getResource方法:在Mockito静态mock的上下文里,所有未显式模拟的静态方法调用会返回默认值(此处getResource返回null)。当getInputString调用Resources.getResource("resources/file.json")时得到null,后续传入asCharSource时触发NullPointerException。
  2. mock匹配逻辑不匹配:测试中用的url是真实资源resources/linearPartners.json的URL,而测试方法传入的是resources/file.json,就算getResource返回值非null,asCharSource的mock也不会被命中。

修复方案

优化后的测试代码

@Test
public void shouldThrowUncheckedIOExceptionWhenResourceLoadFails() throws Exception {
    // 准备一个非null的URL实例(可用任意有效URL,或mock一个)
    URL mockUrl = new URL("https://example.com/test.json");
    
    try (MockedStatic<Resources> resourcesMock = Mockito.mockStatic(Resources.class)) {
        // 模拟getResource方法,匹配测试传入的路径
        resourcesMock.when(() -> Resources.getResource("resources/file.json"))
            .thenReturn(mockUrl);
        
        // 模拟asCharSource方法,直接抛出IOException,无需手动实现CharSource
        resourcesMock.when(() -> Resources.asCharSource(mockUrl, UTF_8))
            .thenThrow(new IOException("Test IO failure"));
        
        // 验证预期异常
        Assertions.assertThrows(UncheckedIOException.class, 
            () -> staticConfigPartnerAliasesPersistenceFacade.getInputString("resources/file.json"));
    }
}

关键修复点

  • 必须模拟Resources.getResource方法,确保返回非null的URL,避免NPE。
  • 让asCharSource的mock直接抛出IOException,比手动实现CharSource更简洁且符合测试意图。
  • 确保getResource的匹配路径和测试时传入的路径一致,保证mock逻辑被命中。

内容的提问来源于stack exchange,提问作者Neha Bhoi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 20:01:54