TestNG测试PkceUtil时Base64.getUrlEncoder()触发空指针异常
测试PkceUtil.generateCodeVerifier时触发NullPointerException的问题
我用TestNG框架测试PkceUtil类的generateCodeVerifier静态方法,这个方法通过Base64.getUrlEncoder()生成URL编码的code verifier,但测试代码执行when(Base64.getUrlEncoder().withoutPadding().encodeToString(any())).thenReturn(codeVerifier);时触发NullPointerException,相关代码、异常堆栈及依赖配置如下,请问测试代码哪里有问题?
待测试代码
public class PkceUtil { private PkceUtil(){ super(); } /** * External application use this method for generating a Base64 URL encoded alphanumeric string of characters * with a minimum length of 43 characters and a maximum length of 128 characters (code verifier). * @return code verifier */ public static String generateCodeVerifier(){ SecureRandom secureRandom = new SecureRandom(); byte[] codeVerifier = new byte[32]; secureRandom.nextBytes(codeVerifier); return Base64.getUrlEncoder().withoutPadding().encodeToString(codeVerifier); } ..
测试代码
import org.mockito.MockedStatic; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.testng.PowerMockTestCase; import org.testng.annotations.Test; import java.util.Base64; import static org.mockito.Mockito.*; import static org.testng.Assert.assertEquals; @PrepareForTest(Base64.class) public class PkceUtilTest extends PowerMockTestCase { @Test public void testGenerateCodeVerifier() { MockedStatic<Base64> mockedStaticBase64 = mockStatic(Base64.class); String codeVerifier= ""; // 触发NPE的行 when(Base64.getUrlEncoder().withoutPadding().encodeToString(any())).thenReturn(codeVerifier); final String codeVerifierReturned = PkceUtil.generateCodeVerifier(); assertEquals(codeVerifierReturned, codeVerifier); verify(Base64.getUrlEncoder().withoutPadding().encodeToString(any())); mockedStaticBase64.close(); } }
异常信息
java.lang.NullPointerException at com.cadit.oidc.sdk.pkce.PkceUtilTest.testGenerateCodeVerifier(PkceUtilTest.java:23) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:132) at org.testng.internal.TestInvoker.invokeMethod(TestInvoker.java:599) at org.testng.internal.TestInvoker.invokeTestMethod(TestInvoker.java:174) at org.testng.internal.MethodRunner.runInSequence(MethodRunner.java:46) at org.testng.internal.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:822) at org.testng.internal.TestInvoker.invokeTestMethods(TestInvoker.java:147) at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:146) at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:128) at java.util.ArrayList.forEach(ArrayList.java:1257) at org.testng.TestRunner.privateRun(TestRunner.java:764) at org.testng.TestRunner.run(TestRunner.java:585) at org.testng.SuiteRunner.runTest(SuiteRunner.java:384) at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:378) at org.testng.SuiteRunner.privateRun(SuiteRunner.java:337) at org.testng.SuiteRunner.run(SuiteRunner.java:286) at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:53)
依赖配置
<dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>7.3.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-testng</artifactId> <version>2.0.7</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>3.5.13</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>3.5.13</version> <scope>test</scope> </dependency>
问题原因
你只mock了Base64静态类本身,但Base64.getUrlEncoder()返回的Base64.Encoder对象并未被mock。Mockito对静态方法的默认mock返回值是null,所以调用withoutPadding()时,因为前置的getUrlEncoder()返回null,直接触发NullPointerException。
链式调用的每一步都需要单独mock返回值,不能直接跳过中间层。
修复后的测试代码
import org.mockito.MockedStatic; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.testng.PowerMockTestCase; import org.testng.annotations.Test; import java.util.Base64; import static org.mockito.Mockito.*; import static org.testng.Assert.assertEquals; @PrepareForTest(Base64.class) public class PkceUtilTest extends PowerMockTestCase { @Test public void testGenerateCodeVerifier() { // 模拟Base64.Encoder的两个实例(原始和去掉padding的) Base64.Encoder mockUrlEncoder = mock(Base64.Encoder.class); Base64.Encoder mockNoPaddingEncoder = mock(Base64.Encoder.class); // 使用try-with-resources自动关闭静态mock,避免资源泄漏 try (MockedStatic<Base64> mockedBase64 = mockStatic(Base64.class)) { // 逐层mock链式调用的返回值 mockedBase64.when(Base64::getUrlEncoder).thenReturn(mockUrlEncoder); when(mockUrlEncoder.withoutPadding()).thenReturn(mockNoPaddingEncoder); String expectedVerifier = "test-pkce-verifier-123"; // 精确匹配byte数组类型的参数 when(mockNoPaddingEncoder.encodeToString(any(byte[].class))).thenReturn(expectedVerifier); // 执行待测试方法 String actualVerifier = PkceUtil.generateCodeVerifier(); // 断言结果一致 assertEquals(actualVerifier, expectedVerifier); // 验证每一层调用都执行了一次 mockedBase64.verify(Base64::getUrlEncoder, times(1)); verify(mockUrlEncoder, times(1)).withoutPadding(); verify(mockNoPaddingEncoder, times(1)).encodeToString(any(byte[].class)); } } }
额外说明
- 使用try-with-resources管理
MockedStatic,自动释放资源,避免内存泄漏 - 用
any(byte[].class)替代any(),参数匹配更精准,避免不必要的匹配错误 - 分开验证链式调用的每一步,确保代码执行流程符合预期
内容的提问来源于stack exchange,提问作者robyp7
相关产品推荐
相关产品推荐

