Export to GitHub

dexmaker - issue #20

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


Posted on Feb 6, 2013 by Happy Rabbit

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(MockSystemInterface.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:555) 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/3738c13b019dcceef42884ea79f09c92b374f0a1 .

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.

Attachments

Comment #1

Posted on Feb 9, 2013 by Massive Bear

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! } }; }

Comment #2

Posted on Feb 11, 2013 by Happy Rabbit

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).

Comment #3

Posted on Feb 26, 2013 by Massive Bear

https://groups.google.com/forum/?fromgroups=#!topic/mockito/pmAzNAnVOmE

Comment #4

Posted on Mar 2, 2013 by Massive Bear

Waiting on our Mockito friends to decide if they're fixing this or we are.

Status: Accepted

Labels:
Type-Defect Priority-Medium