SSLContext 模拟未按预期运行

SSLContext mock not behaving as expected

我有以下简单的 class: 导入 javax.net.ssl.SSLContext;

public class AClass {
    public void someMethod() throws Exception {
        SSLContext context = SSLContext.getInstance("SSL");
        context.init(null, null, null);
    }
}

及其 JUnit: 导入 javax.net.ssl.SSLContext;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ SSLContext.class })
public class ATest {
    @Test
    public void testSomeMethod() throws Exception {
        PowerMockito.mockStatic(SSLContext.class);
        SSLContext context = Mockito.mock(SSLContext.class);
        Mockito.when(context.getInstance("SSL")).thenReturn(context);
        new AClass().someMethod();
    }
}

JUnit 失败并显示以下堆栈跟踪:

java.lang.NullPointerException
    at javax.net.ssl.SSLContext.init(Unknown Source)
    at random.AClass.someMethod(AClass.java:8)
    at random.ATest.testSomeMethod(ATest.java:20)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:316)
    at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:89)
    at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:97)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:300)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:131)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.access0(PowerMockJUnit47RunnerDelegateImpl.java:59)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner$TestExecutorStatement.evaluate(PowerMockJUnit47RunnerDelegateImpl.java:147)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.evaluateStatement(PowerMockJUnit47RunnerDelegateImpl.java:107)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTest(PowerMockJUnit47RunnerDelegateImpl.java:82)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:288)
    at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:87)
    at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:50)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:208)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:147)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:121)
    at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
    at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:123)
    at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:121)
    at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
    at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:59)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

堆栈跟踪指向带有空指针的 context.init(null, null, null); 行。但是当我调试时(在 Eclipse 中),我可以清楚地看到 context 变量的值是 Mock for SSLContext, hashCode: 1857173583。如果是模拟,那么像 init() 这样的 void 方法不应该做任何事情。那么,为什么它会抛出 NullPointerException?

正在查看 API and decompiled signature for the init method which throws the NPE, it shows as final, which basic Mockito.mock() can not handle

另一方面,PowerMockito.mock() 的 javadoc 为:

org.powermock.api.mockito.PowerMockito

public static T mock(Class type)
Creates a mock object that supports mocking of final and native methods.

Type Parameters:
    T - the type of the mock object
Parameters:
    type - the type of the mock object
Returns:
    the mock object.

所以,稍微改变一下您的测试应该可以正常工作:

@RunWith(PowerMockRunner.class)
@PrepareForTest({SSLContext.class})
public class ATest {
    @Test
    public void testSomeMethod() throws Exception {
        // create the mock to return by getInstance()
        SSLContext context = PowerMockito.mock(SSLContext.class);

        // mock the static method getInstance() to return above created mock context
        PowerMockito.mockStatic(SSLContext.class);
        Mockito.when(SSLContext.getInstance("SSL")).thenReturn(context);

        // invoke the object under test
        new AClass().someMethod();

        //TODO - add verifications / assertions
    }
}

更新:

因为你是运行测试PowerMockRunner,你也可以替换

SSLContext context = PowerMockito.mock(SSLContext.class);

有一个字段

@Mock
private SSLContext context;

这也将由 PowerMock 处理(或者如果您只需要基本的 mockito,则使用 MockitoJUnitRunner