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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 07:55:19