Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NotAMockException thrown by tests in different test classes due to separate classloader #593

Open
dkharlan opened this issue Nov 18, 2015 · 10 comments
Labels

Comments

@dkharlan
Copy link

I'm running into a NotAMockException thrown from Mockito.verify() when using PowerMockito with Robolectric. It occurs when I'm trying to mock out parts of a third-party library; I've written some code to reproduce the issue.

Here are two classes that match the interface of the library I'm mocking:

public final class SingletonAPI {

    private static SingletonAPI instance;

    private SingletonAPI() {
    }

    public void doStuff() {
        // Something side-effecty here
    }

    public static SingletonAPI getAPI() {
        if(instance == null) {
            instance = new SingletonAPI();
        }
        return instance;
    }
}
public class Methods {

    public static int utilityMethod(int a) {
        return a + 10;
    }
}

And here are tests that reproduce the issue:

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
@PrepareForTest(Methods.class)
public class TestB {

    @Rule
    public PowerMockRule rule = new PowerMockRule();

    @Test
    public void testB1() {

        PowerMockito.mockStatic(Methods.class);
        Mockito.when(Methods.utilityMethod(10)).thenReturn(20);

        // buried in additional code
        Methods.utilityMethod(10);

        PowerMockito.verifyStatic(Mockito.times(1));
        Methods.utilityMethod(10);
    }
}
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
@PrepareForTest(SingletonAPI.class)
public class TestC {

    @Rule
    public PowerMockRule rule = new PowerMockRule();

    @Test
    public void testC1() {

        SingletonAPI mockAPI = PowerMockito.mock(SingletonAPI.class);
        PowerMockito.mockStatic(SingletonAPI.class);
        PowerMockito.when(SingletonAPI.getAPI()).thenReturn(mockAPI);

        // buried in additional code
        SingletonAPI.getAPI().doStuff();

        Mockito.verify(mockAPI).doStuff();  // NotAMockException thrown here
    }
}

Both of these tests pass if run in isolation. However, if run together (with an IDE or with gradle test) then a NotAMockException is thrown by Mockito.verify(mockAPI).doStuff();.

The order these are run in matters (which is why these tests classes are named TestB and TestC; if TestC were renamed to TestA then the issue disappears since apparently IntelliJ IDEA likes to run these in alphabetical order). I've spent some time poking around in the debugger, and what I found was that the following condition from CglibMockMaker.getHandler():

// CglibMockMaker from PowerMock 1.6.3, line 46
if (!(callback instanceof MethodInterceptorFilter)) {
    return null;
}

becomes true since callback and MethodInterceptorFilter in this particular case were loaded by different instances of MockClassLoader, presumably due to the two instances of PowerMockRule.

Finally: is this a PowerMock issue, or a Robolectric issue? I figured I'd start here due to the PowerMockRule/MockClassLoader apparatus's involvement, but let me know if this is not the appropriate forum for this (or if it turns out this is just developer error :)

@mboudraa
Copy link

mboudraa commented Jan 8, 2016

I have the same bug in the exact same configuration

@powturns
Copy link

I'm also experiencing this. Any workarounds the classloader issue would be greatly appreciated.

I also discovered this stack overflow illustrating the same issue:
http://stackoverflow.com/questions/34343601/powermockito-notamockexception-on-a-mock

From what I can tell, this is whats going on:

  1. The first test class begins execution
  2. PowerMockRule creates a new MockClassLoader in the apply method
  3. tests execute normally
  4. The second test class begins execution
  5. PowerMockRule creates a new MockClassLoader in the apply method for this test class
  6. Newly mocked objects are created using the classloader in (5), but org.powermock.api.mockito.internal.mockcreation.PowerMockMethodInterceptorFilter is still bound to the classloader in (2), so the instanceof comparison fails

I've tried a couple of different hacks to try to get PowerMockMethodInterceptorFilter loading using the new classloader, but nothing so far.

@rtmvc
Copy link

rtmvc commented Feb 3, 2016

Same issue. Are there any solution or workaround so far?

@powturns
Copy link

powturns commented Feb 3, 2016

I found that for some classes, I could use Mockito.mock rather than PowerMockito.mock and it would work, even though I was mocking a final class.

For other unit test classes, unless I explicitly need the robolectric stubbing I've switched to using the PowerkMock test runner.

Between these two things I've managed to hack my way through, but it would be nice to have a real solution

@fonkap
Copy link

fonkap commented Feb 22, 2016

Please, can you try using this:

@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "org.powermock.*"})

Bytheway I built a single little example that reproduces the problem (And is solved with the line above)

package nf.co.fonk;

import junit.framework.Assert;
import org.junit.Rule;
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.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest;
import org.powermock.modules.junit4.rule.PowerMockRule;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;
import android.os.Handler;

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
//This will make the test work
//@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "org.powermock.*"})
@PrepareOnlyThisForTest({ServiceCallbackBase.class, Dummy.class})
public class MainActivityTest {

    @Rule
    public PowerMockRule rule = new PowerMockRule();

    @Test
    public void test1() throws Exception {
        try {
            //This Mockito.withSettings() thing is important to make the test fail!
            ServiceCallbackBase callback = PowerMockito.mock( ServiceCallbackBase.class, Mockito.withSettings());

            callback.dispatchMessage(null);
            Mockito.verify(callback).dispatchMessage(null);

        } catch (Exception e){
            e.printStackTrace();
            Assert.fail();
        }

    }
}

final class ServiceCallbackBase extends Handler {

}

@powturns
Copy link

Some Observations with the above fix
If you are using org.powermock:powermock-classloading-objenesis rather than org.powermock:powermock-classloading-xstream PowerMock throws internal exceptions.

Even when using org.powermock:powermock-classloading-xstream in the second test class, with the powermock rule, I'm getting the following exception:

org.mockito.exceptions.base.MockitoException: 
ClassCastException occurred while creating the mockito proxy :
  class to mock : 'org.eclipse.paho.android.service.MqttAndroidClient', loaded by classloader : 'org.powermock.core.classloader.MockClassLoader@af5f38'
  created class : 'org.eclipse.paho.android.service.MqttAndroidClient$$EnhancerByMockitoWithCGLIB$$9b4a9912', loaded by classloader : 'org.mockito.internal.creation.util.SearchingClassLoader@16328b2'
  proxy instance class : 'org.eclipse.paho.android.service.MqttAndroidClient$$EnhancerByMockitoWithCGLIB$$9b4a9912', loaded by classloader : 'org.mockito.internal.creation.util.SearchingClassLoader@5bf5ac'
  instance creation by : ObjenesisInstantiator

The first Test class that uses the PowerMock rule executes as expected.

Relevant build.gradle:

testCompile 'org.mockito:mockito-core:1.10.19'

    testCompile ('org.powermock:powermock-api-mockito:1.6.4') {
        exclude module: 'hamcrest-core'
        exclude module: 'objenesis'
    }

    testCompile ('org.powermock:powermock-module-junit4:1.6.4') {
        exclude module: 'hamcrest-core'
        exclude module: 'objenesis'
    }

    testCompile ('org.powermock:powermock-module-junit4-rule:1.6.4') {
        exclude module: 'hamcrest-core'
        exclude module: 'objenesis'
    }

    testCompile ('org.powermock:powermock-classloading-xstream:1.6.4') {
        exclude module: 'hamcrest-core'
        exclude module: 'objenesis'
    }

@jcogilvie
Copy link

I can confirm that @fonkap's solution has fixed it for me. (I'm the poster of that SO question linked above.)

@dkharlan
Copy link
Author

This also solves the issue for me, although I ran into two things:

  1. You'll need to add "org.powermock.*" to your @PowerMockIgnore for each test class, otherwise it'll still create multiple class loaders.
  2. I ran into the same MockitoException as @51systems; the fix for me was to disable the Objenesis cache by creating the following class (with identical package name and class name):
package org.mockito.configuration;

public class MockitoConfiguration extends DefaultMockitoConfiguration {

    @Override
    public boolean enableClassCache() {
        return false;
    }
}

Thanks for the help, guys! I'm going to leave this open for now since this seems more like a workaround rather than a fix per se; if one of the PowerMock devs wants to consider this the officially sanctioned solution then feel free to close.

@jjNford
Copy link

jjNford commented May 31, 2016

I can verity that @dkharlan solution works but the work around seems like a 'hack' and should be handled by PowerMock. @jayway any plans to resolve this given the growing use of PowerMock?

@andy9775
Copy link

I just want to add to confirm that adding "org.powermock.*" to all @PowerMockIgnore annotations solves this issue for now.

To add some more info, I experienced this a few days ago and changing the method name of the failing test caused the issue to go away (odd behaviour), but ultimately adding more tests brought it back. I had objenesis disabled the entire time as well and I have no idea what the method name had to do with the problem that was occurring.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants