greyblue9 / dexmaker

Automatically exported from code.google.com/p/dexmaker
1 stars 0 forks source link

DexMaker+Mockito: Spying objects with package-private method throws IllegalAccessError #26

Open GoogleCodeExporter opened 8 years ago

GoogleCodeExporter commented 8 years ago
What steps will reproduce the problem?
1. Install the latest DexMaker, Mockito
2. Write a class under test which has package-private methods, as follows:
----
package com.example.mockitolearning;

public class ClassWithPackagePrivate {

    public ClassWithPackagePrivate() {

    }

    int add(int x, int y) {
        return x + y;
    }
}
----
3. Write a test code which belongs to the same package as follows
----
package com.example.mockitolearning;  // <-- same package as class under test

import static org.mockito.Mockito.*;
import junit.framework.TestCase;

public class ClassWithPackagePrivateTest extends TestCase {

    protected void setUp() throws Exception {
        super.setUp();
    }

    public void testAdd() {
        ClassWithPackagePrivate p = spy(new ClassWithPackagePrivate());
        p.add(1, 2);
    }

    protected void tearDown() throws Exception {
        super.tearDown();
    }

}
----

What is the expected output? What do you see instead?

Expected: The above test should pass.
Instead: It throws IllegalAccessError. The stack trace is as follows:
----
java.lang.IllegalAccessError: tried to access method 
com.example.mockitolearning.ClassWithPackagePrivate.add:(II)I from class 
ClassWithPackagePrivate_Proxy
at 
ClassWithPackagePrivate_Proxy.super$add$int(ClassWithPackagePrivate_Proxy.genera
ted)
at java.lang.reflect.Method.invokeNative(Native Method)
at com.google.dexmaker.stock.ProxyBuilder.callSuper(ProxyBuilder.java:523)
at 
com.google.dexmaker.mockito.InvocationHandlerAdapter$ProxiedMethod.invoke(Invoca
tionHandlerAdapter.java:98)
at 
org.mockito.internal.invocation.InvocationImpl.callRealMethod(InvocationImpl.jav
a:108)
at 
org.mockito.internal.stubbing.answers.CallsRealMethods.answer(CallsRealMethods.j
ava:36)
at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:93)
at 
org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:2
9)
at 
org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifier
Handler.java:38)
at 
com.google.dexmaker.mockito.InvocationHandlerAdapter.invoke(InvocationHandlerAda
pter.java:54)
at ClassWithPackagePrivate_Proxy.add(ClassWithPackagePrivate_Proxy.generated)
at 
com.example.mockitolearning.ClassWithPackagePrivateTest.testAdd(ClassWithPackage
PrivateTest.java:14)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:190)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:175)
at 
android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:55
5)
at 
android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1661)
----

What version of the product are you using? On what operating system?
Device: Nexus7 2012 with Android 4.2.1.
Jars: 
  mockito-all-1.9.5.jar
  dexmaker-1.0.jar
  dexmaker-mockito-1.0.jar

Please provide any additional information below.
- If we don't wrap spy(...), the above test will be passed.
- If we change the modifier of "add(int x, int y)" into "protected", the test 
will be passed.

Original issue reported on code.google.com by jun.nama@gmail.com on 25 Sep 2013 at 11:04

GoogleCodeExporter commented 8 years ago
One workaround for this is for dexmaker to avoid calling super class methods 
that are "uncallable".

This will have the (possibly undesirable) effect of uncallable methods not 
being called, which could lead to an out-of-state object but this is often 
preferable to complete failure.

I have a patched version of dexmaker with this change here:

https://code.google.com/r/jp-dexmaker/

Specifically:

https://code.google.com/r/jp-dexmaker/source/browse/dexmaker/src/main/java/com/g
oogle/dexmaker/stock/ProxyBuilder.java#523

Original comment by j...@sharethis.com on 11 Dec 2013 at 12:54

GoogleCodeExporter commented 8 years ago
Unfortunately package-private only works if you can load the mock class in the 
same class loader as the target class. We don't have a hack that'll work on 
Dalvik to do this. If you'd like to attempt one, please do!

Original comment by limpbizkit on 11 Dec 2013 at 1:10

GoogleCodeExporter commented 8 years ago
In the cases I've encountered so far this issue presents when trying to spy 
Android framework classes (famously when mocking views and their children), 
which call internal package-private methods during initialization.  Simply 
avoiding invocation on these "guaranteed to fail" methods allows for the view 
to be mocked, but obviously does have the effect of not "truly" representing 
the internal state of the object within the test.

Depending on the test this is "usually" ok because the mock is typically being 
used simply to orchestrate callable methods and not used as a "real" view 
(who's behavior may have been changed as a result of NOT calling its own 
internal methods).

Original comment by j...@sharethis.com on 11 Dec 2013 at 1:15