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

如何Mock FileReader及Kubernetes Java客户端内部方法

Kubernetes Java客户端ApiClient Bean单元测试NPE问题

待测试方法

@Bean
public ApiClient apiClient() throws IOException {
    try (FileReader fileReader = new FileReader(kubeConfigPath)) {
        final ApiClient client = ClientBuilder.kubeconfig(KubeConfig.loadKubeConfig(fileReader)).build();
        final OkHttpClient httpClient = client.getHttpClient().newBuilder()
                .readTimeout(0, TimeUnit.SECONDS)
                .writeTimeout(WRITE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
                .connectTimeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
                .build();
        client.setHttpClient(httpClient);
        return client;
    }
}

当前测试用例

@Test
public void testApiClientConfigurations() throws IOException {
    FileReader fileReader = Instancio.create(FileReader.class);
    KubeConfig kubeConfig = Instancio.create(KubeConfig.class);

    OkHttpClient.Builder httpClientBuilder = mock(OkHttpClient.Builder.class);
    OkHttpClient httpClient = mock(OkHttpClient.class);

    when(KubeConfig.loadKubeConfig(fileReader)).thenReturn(kubeConfig);
    when(apiClient.getHttpClient()).thenReturn(httpClient);
    when(httpClient.newBuilder()).thenReturn(httpClientBuilder);
    when(httpClientBuilder.readTimeout(0, TimeUnit.SECONDS)).thenReturn(httpClientBuilder);
    when(httpClientBuilder.writeTimeout(10, TimeUnit.SECONDS)).thenReturn(httpClientBuilder);
    when(httpClientBuilder.connectTimeout(10, TimeUnit.SECONDS)).thenReturn(httpClientBuilder);
    when(httpClientBuilder.build()).thenReturn(httpClient);
    when(apiClient.setHttpClient(httpClient)).thenReturn(apiClient);

    //Act
    kubernetesConfig.apiClient();
}

遇到的错误

java.lang.NullPointerException: Cannot invoke "sun.nio.cs.StreamDecoder.read(char[], int, int)" because "this.sd" is null

    at java.base/java.io.InputStreamReader.read(InputStreamReader.java:177)
    at org.yaml.snakeyaml.reader.StreamReader.update(StreamReader.java:180)
    at org.yaml.snakeyaml.reader.StreamReader.ensureEnoughData(StreamReader.java:173)
    at org.yaml.snakeyaml.reader.StreamReader.ensureEnoughData(StreamReader.java:168)
    at org.yaml.snakeyaml.reader.StreamReader.peek(StreamReader.java:123)
    at org.yaml.snakeyaml.scanner.ScannerImpl.scanToNextToken(ScannerImpl.java:1204)
    at org.yaml.snakeyaml.scanner.ScannerImpl.fetchMoreTokens(ScannerImpl.java:320)
    at org.yaml.snakeyaml.scanner.ScannerImpl.checkToken(ScannerImpl.java:238)
    at org.yaml.snakeyaml.parser.ParserImpl$ParseImplicitDocumentStart.produce(ParserImpl.java:210)
    at org.yaml.snakeyaml.parser.ParserImpl.peekEvent(ParserImpl.java:161)
    at org.yaml.snakeyaml.parser.ParserImpl.checkEvent(ParserImpl.java:152)
    at org.yaml.snakeyaml.composer.Composer.getSingleNode(Composer.java:156)
    at org.yaml.snakeyaml.constructor.BaseConstructor.getSingleData(BaseConstructor.java:178)
    at org.yaml.snakeyaml.Yaml.loadFromReader(Yaml.java:493)
    at org.yaml.snakeyaml.Yaml.load(Yaml.java:446)
    at io.kubernetes.client.util.KubeConfig.loadKubeConfig(KubeConfig.java:89)
    at config.KubernetesConfigTest.testApiClientConfigurations(KubernetesConfigTest.java:98)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

问题

我正在为上述apiClient()方法编写单元测试,使用Kubernetes Java客户端依赖,当前测试抛出空指针异常,不清楚如何Mock方法内部调用的静态方法及链式调用逻辑。

解决方案

问题根源

  1. Instancio创建的FileReader是未初始化的空实例,KubeConfig.loadKubeConfig会尝试读取该Reader,导致底层流解码器为空触发NPE。
  2. 缺少ClientBuilder.kubeconfig(kubeConfig).build()的Mock逻辑,测试中直接使用未定义的apiClient变量,逻辑不完整。

修正后的测试用例

确保测试类添加@ExtendWith(MockitoExtension.class),并使用Mockito静态Mock支持(需Mockito 3.4+版本):

@Test
public void testApiClientConfigurations() throws IOException {
    // Mock核心依赖对象
    KubeConfig mockKubeConfig = mock(KubeConfig.class);
    ApiClient mockApiClient = mock(ApiClient.class);
    OkHttpClient mockOkHttpClient = mock(OkHttpClient.class);
    OkHttpClient.Builder mockHttpClientBuilder = mock(OkHttpClient.Builder.class);
    ClientBuilder mockClientBuilder = mock(ClientBuilder.class);

    // 静态Mock KubeConfig.loadKubeConfig,匹配任意Reader入参
    try (MockedStatic<KubeConfig> mockedKubeConfig = mockStatic(KubeConfig.class)) {
        mockedKubeConfig.when(() -> KubeConfig.loadKubeConfig(any(Reader.class))).thenReturn(mockKubeConfig);

        // 静态Mock ClientBuilder链式调用
        try (MockedStatic<ClientBuilder> mockedClientBuilder = mockStatic(ClientBuilder.class)) {
            mockedClientBuilder.when(() -> ClientBuilder.kubeconfig(mockKubeConfig)).thenReturn(mockClientBuilder);
            when(mockClientBuilder.build()).thenReturn(mockApiClient);

            // Mock OkHttpClient相关配置逻辑
            when(mockApiClient.getHttpClient()).thenReturn(mockOkHttpClient);
            when(mockOkHttpClient.newBuilder()).thenReturn(mockHttpClientBuilder);
            when(mockHttpClientBuilder.readTimeout(0, TimeUnit.SECONDS)).thenReturn(mockHttpClientBuilder);
            when(mockHttpClientBuilder.writeTimeout(WRITE_TIMEOUT_SECONDS, TimeUnit.SECONDS)).thenReturn(mockHttpClientBuilder);
            when(mockHttpClientBuilder.connectTimeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS)).thenReturn(mockHttpClientBuilder);
            when(mockHttpClientBuilder.build()).thenReturn(mockOkHttpClient);
            doReturn(mockApiClient).when(mockApiClient).setHttpClient(mockOkHttpClient);

            // 执行测试方法
            ApiClient result = kubernetesConfig.apiClient();

            // 验证配置逻辑是否正确执行
            assertNotNull(result);
            verify(mockApiClient).setHttpClient(mockOkHttpClient);
            verify(mockHttpClientBuilder).readTimeout(0, TimeUnit.SECONDS);
            verify(mockHttpClientBuilder).writeTimeout(WRITE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
            verify(mockHttpClientBuilder).connectTimeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
        }
    }
}

关键说明

  • 使用MockedStatic拦截静态方法KubeConfig.loadKubeConfig和ClientBuilder.kubeconfig,避免实际文件读取和客户端初始化。
  • 直接引用类常量WRITE_TIMEOUT_SECONDS和CONNECT_TIMEOUT_SECONDS,保证测试与生产代码的参数一致性。
  • 增加断言和验证逻辑,确保超时配置、HttpClient设置等核心逻辑正确执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 20:14:51