如何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方法内部调用的静态方法及链式调用逻辑。
解决方案
问题根源
- Instancio创建的
FileReader是未初始化的空实例,KubeConfig.loadKubeConfig会尝试读取该Reader,导致底层流解码器为空触发NPE。 - 缺少
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
相关产品推荐
相关产品推荐

