qdrzwd / dexmaker

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

DexMaker+Mockito: "org.mockito.internal.util.MockitoMock is not visible from class loader" #20

Open GoogleCodeExporter opened 8 years ago

GoogleCodeExporter commented 8 years ago
What steps will reproduce the problem?
1. Install a current build of Mockito, DexMaker.
2. Run a test which creates a mock of something in java.lang or android.os

A very simple affected test would be:

public class MockSystemInterface extends InstrumentationTestCase {
  public void test() {
    Mockito.mock(Comparable.class);
  }
}

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

The above test should pass. Instead, it fails with an error from inside 
DexMaker:

java.lang.IllegalArgumentException: org.mockito.internal.util.MockitoMock is 
not visible from class loader
at java.lang.reflect.Proxy.getProxyClass(Proxy.java:111)
at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:212)
at 
com.google.dexmaker.mockito.DexmakerMockMaker.createMock(DexmakerMockMaker.java:
49)
at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:32)
at org.mockito.internal.MockitoCore.mock(MockitoCore.java:55)
at org.mockito.Mockito.mock(Mockito.java:1243)
at org.mockito.Mockito.mock(Mockito.java:1120)
at 
com.google.tests.dexmaker.classloader.MockSystemInterface.test(MockSystemInterfa
ce.java:10)
at java.lang.reflect.Method.invokeNative(Native Method)
at 
android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:214)
at 
android.test.InstrumentationTestCase.runTest(InstrumentationTestCase.java:199)
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?

Current build of Mockito and DexMaker; running on emulated Android 4.2. This 
does not yet affect released versions of Mockito.

Please provide any additional information below.

Mockito asks that the objects created by DexmakerMockMaker implement this 
internal interface MockitoMock, which it subsequently uses to decide whether an 
object is a mock or not. This change was made in December 2012, and is not yet 
in any released versions Mockito. The change can be found at 
https://github.com/mockito/mockito/commit/3738c13b019dcceef42884ea79f09c92b374f0
a1 .

DexmakerMockMaker creates its proxy using the ClassLoader of the Class to be 
mocked. In this case, the Class to be mocked uses the system library 
ClassLoader, which cannot load application classes.

I'd suggest changing DexmakerMockMaker to use the context ClassLoader instead, 
which should normally have access to all the classes that the test case has 
access to. I've attached a patch for this, and verified that it fixes this 
particular test. However I'm having trouble running the rest of the DexMaker 
tests, so I'm not sure if it might break something else.

Original issue reported on code.google.com by i...@google.com on 6 Feb 2013 at 1:59

Attachments:

GoogleCodeExporter commented 8 years ago
I ran the code on-device with the latest Mockito and it worked just fine.

One potential issue: this may prevent us from mocking package-private methods. 
I don't think this is a big deal 'cause we're already broken for 
package-private methods - we don't generate code in the correct package; 
everything just goes in the default package.

According to the Mockito source, they aggregate multiple class loaders to do 
their dirty work. Mockito combines the mocked type's class loader, Mockito's 
class loader, and the context class loader. That's a bit nasty!
https://github.com/mockito/mockito/blob/master/src/org/mockito/internal/creation
/jmock/SearchingClassLoader.java

I think their extra class loader guarantees that they won't work with 
package-private methods due to the same classloader rule. They have a unit test 
that suggests otherwise, MockingPackageProtectedTest.java. I wonder whether 
they support mocking package-private  methods? It's definitely broken in OSGi 
but I'm surprised it works elsewhere.
https://code.google.com/p/mockito/issues/detail?id=127

Next steps: decide whether we should combine class loaders. We could probably 
do it in a few lines of code:

  public ClassLoader combine(final ClassLoader... loaders) {
    return new ClassLoader() {
      @Override protected Class<?> findClass(String name) throws ClassNotFoundException {
        for (int i = 0; i < loaders.length - 1; i++) {
          try {
            return loaders[i].loadClass(name);
          } catch (ClassNotFoundException ignored) {
          }
        }
        return loaders[loaders.length - 1].loadClass(name); // Don't ignore failures from the last loader!
      }
    };
  }

Original comment by limpbizkit on 9 Feb 2013 at 3:00

GoogleCodeExporter commented 8 years ago
That's not a bit nasty, that's a lot nasty!

My understanding of ClassLoaders is fairly rusty and I'm not quite sure I've 
got my head wrapped around this package-private method problem.

My patch only applies to mocking interfaces, not concrete classes, I think? 
Playing with a little toy code, it looks to me like using the context 
ClassLoader still allows us to proxy package-private interfaces (so long as 
nobody's mucked with the context ClassLoader too much).

A delegating ClassLoader like yours does seem fail to create a package-private 
proxy, as does a chaining ClassLoader that sets the ClassLoaders up as parents 
of each other (like Mockito's).

Original comment by i...@google.com on 11 Feb 2013 at 1:11

GoogleCodeExporter commented 8 years ago
https://groups.google.com/forum/?fromgroups=#!topic/mockito/pmAzNAnVOmE

Original comment by limpbizkit on 26 Feb 2013 at 12:08

GoogleCodeExporter commented 8 years ago
Waiting on our Mockito friends to decide if they're fixing this or we are.

Original comment by limpbizkit on 2 Mar 2013 at 2:43