linkedin / dexmaker

A utility for doing compile or runtime code generation targeting Android's Dalvik VM
Apache License 2.0
1.86k stars 248 forks source link

mockStatic PendingIntent got NPE #168

Closed paulhu0819 closed 4 years ago

paulhu0819 commented 4 years ago

I tried to use mockStatic to mock PendingIntent class in my test but it got NPE immediately after called startMonitoring().

Code:

@Before
fun setUp() {
     session = mockitoSession()
             .initMocks(this)
             .mockStatic(PendingIntent::class.java)
             .strictness(Strictness.LENIENT)
             .startMocking()
}

Logs:

06-10 16:00:53.355 27681 27703 E TestRunner: java.lang.NullPointerException: Attempt to invoke interface method 'android.os.IBinder android.content.IIntentSender.asBinder()' on a null object reference
06-10 16:00:53.355 27681 27703 E TestRunner:    at android.app.PendingIntent.hashCode(PendingIntent.java:1192)
06-10 16:00:53.355 27681 27703 E TestRunner:    at java.util.HashMap.hash(HashMap.java:338)
06-10 16:00:53.355 27681 27703 E TestRunner:    at java.util.HashMap.put(HashMap.java:611)
06-10 16:00:53.355 27681 27703 E TestRunner:    at com.android.dx.mockito.inline.InlineStaticMockMaker.createMock(InlineStaticMockMaker.java:167)
06-10 16:00:53.355 27681 27703 E TestRunner:    at com.android.dx.mockito.inline.MockMakerMultiplexer.createMock(MockMakerMultiplexer.java:65)
06-10 16:00:53.355 27681 27703 E TestRunner:    at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:35)
06-10 16:00:53.355 27681 27703 E TestRunner:    at org.mockito.internal.MockitoCore.mock(MockitoCore.java:69)
06-10 16:00:53.355 27681 27703 E TestRunner:    at org.mockito.Mockito.mock(Mockito.java:1905)
06-10 16:00:53.355 27681 27703 E TestRunner:    at org.mockito.Mockito.mock(Mockito.java:1814)
06-10 16:00:53.355 27681 27703 E TestRunner:    at com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder.lambda$mockStatic$0(StaticMockitoSessionBuilder.java:58)
06-10 16:00:53.355 27681 27703 E TestRunner:    at com.android.dx.mockito.inline.extended.-$$Lambda$StaticMockitoSessionBuilder$AoyQNqvLdWW5bc2GJ4H8RWXvBuo.get(Unknown Source:2)
06-10 16:00:53.355 27681 27703 E TestRunner:    at com.android.dx.mockito.inline.extended.StaticMockitoSession.mockStatic(StaticMockitoSession.java:98)
06-10 16:00:53.355 27681 27703 E TestRunner:    at com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder.startMocking(StaticMockitoSessionBuilder.java:144)
06-10 16:00:53.355 27681 27703 E TestRunner:    at com.android.networkstack.tethering.TetheringNotificationUpdaterTest.setUp(TetheringNotificationUpdaterTest.kt:148)

I have looked into the NPE, the problem is that InlineStaticMockMaker uses HashMap to store mock object data that will call to real PendingIntent.hascode() when trying to put mock PendingIntent object as a HashMap key. To solve the NPE, I suggest InlineStaticMockMaker may use MockMap like InlineDexmakerMockMaker to store mock object data safely.

dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/InlineStaticMockMaker.java

    /**
     * All currently active mock markers. We modify the class's byte code. Some objects of the class
     * are modified, some are not. This list helps the {@link MockMethodAdvice} help figure out if a
     * object's method calls should be intercepted.
     */
    private final HashMap<Object, InvocationHandlerAdapter> markerToHandler = new HashMap<>();
    private final Map<Class, Object> classToMarker = new HashMap<>(); /***** Should use MockMap instead. *****/

....

    @Override
    public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
        Class<T> typeToMock = settings.getTypeToMock();
        if (!typeToMock.equals(mockingInProgressClass.get()) || Modifier.isAbstract(typeToMock
                .getModifiers())) {
            return null;
        }

        Set<Class<?>> interfacesSet = settings.getExtraInterfaces();
        InvocationHandlerAdapter handlerAdapter = new InvocationHandlerAdapter(handler);

        classTransformer.mockClass(MockFeatures.withMockFeatures(typeToMock, interfacesSet));

        Instantiator instantiator = Mockito.framework().getPlugins().getDefaultPlugin
                (InstantiatorProvider2.class).getInstantiator(settings);

        T mock;
        try {
            mock = instantiator.newInstance(typeToMock);
        } catch (org.mockito.creation.instance.InstantiationException e) {
            throw new MockitoException("Unable to create mock instance of type '" + typeToMock
                    .getSimpleName() + "'", e);
        }

        if (classToMarker.containsKey(typeToMock)) {
            throw new MockitoException(typeToMock + " is already mocked");
        }
        classToMarker.put(typeToMock, mock); /***** Got NPE here *****/

        markerToHandler.put(mock, handlerAdapter);
        return mock;
    }
chao2zhang commented 4 years ago

Hey @paulhu0819 , thanks for reporting this bug. I have successfully reproduced it and going to work on your fix considering your suggestions.