Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking static classes with Powermock2 and Kotlin

in Android I've been using the version 1.6.1 of Powermock and all this implementation worked really good for statics. It's not working now at all when I changed to 2.0.0-beta.5. Indeed, it didn't even work upgrading from my previous 1.6.1 to 1.7.1.

I have this implementation:

// Power Mockito
testImplementation "org.powermock:powermock-api-mockito2:2.0.0-beta.5"
testImplementation "org.powermock:powermock-module-junit4-rule-agent:2.0.0-beta.5"
testImplementation "org.powermock:powermock-module-junit4:2.0.0-beta.5"
//testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.0-beta.5'

// Mockito
testImplementation "org.mockito:mockito-core:2.11.0"
testImplementation "com.nhaarman:mockito-kotlin-kt1.1:1.5.0"
androidTestImplementation("com.nhaarman:mockito-kotlin-kt1.1:1.5.0", {
    exclude group: 'org.mockito', module: 'mockito-core'
})
androidTestImplementation 'org.mockito:mockito-android:2.11.0'

and I'm trying to mock a static the same way I was doing with 1.6.1:

@RunWith(PowerMockRunner::class)
@PrepareForTest(SharedPreferencesHelper.Companion::class, ConfigUseCaseTests::class)
class ConfigUseCaseTests {

    lateinit var context: Context

    @Before
    fun setUp() {
        context = mock()
    }

    @Test
    fun getConfigs_fromJson() {
        PowerMockito.mockStatic(SharedPreferencesHelper.Companion::class.java)

        val instance = mock<SharedPreferencesHelper.Companion>()
        doReturn("foo")
                .whenever(instance)
                .loadString(isA(), anyString(), anyString(), anyString())
//        whenever(instance.loadString(isA(), anyString(), anyString(), anyString())).thenReturn("foo") // This shows the same error
        PowerMockito.whenNew(SharedPreferencesHelper.Companion::class.java)
                .withAnyArguments()
                .thenReturn(instance)

        val mockedFoo = instance.loadString(context, "", "", "") // This shows "foo"

        val mockedCompanion = SharedPreferencesHelper.loadString(context, "", "", "") // This is throwing NullPointerException

        Assert.assertEquals(mockedCompanion, "foo")
    }
}

My SharedPreferencesHelper looks like:

class SharedPreferencesHelper {
    companion object {

        @Suppress("NON_FINAL_MEMBER_IN_OBJECT")
        open fun loadString(context: Context, fileName: String, key: String, defaultValue: String?): String? {
            val sharedPreferences = getWithFileName(context, fileName)
            return if (!sharedPreferences.contains(key)) {
                defaultValue
            } else {
                sharedPreferences.getString(key, defaultValue)
            }
        }
    }
}

I've tried to play with the open but it didn't work.

Exception: (I don't understand it at all)

java.lang.NullPointerException
    at my.package.ConfigUseCaseTests.getConfigs_fromJson(ConfigUseCaseTests.kt:45)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:326)
    at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:89)
    at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:97)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:310)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:131)

I can say, sometimes IT WORKS!! I'm adding the video because it looks amazing that it happens just sometimes: https://youtu.be/YZObVLcERBo (watch at the middle and the end)

like image 921
Rafael Ruiz Muñoz Avatar asked Jun 08 '18 09:06

Rafael Ruiz Muñoz


People also ask

Can static classes be mocked?

Because there is no instance variable, the class name itself should be used to access the members of a static class. The powerful capabilities of the feature-rich JustMock framework allow you to mock static classes and calls to static members like methods and properties, set expectations and verify results.

Can we use Mockito and PowerMock together?

Of course you can – and probably will – use Mockito and PowerMock in the same JUnit test at some point of time.

Can we mock static methods in Mockito?

Since static method belongs to the class, there is no way in Mockito to mock static methods.


1 Answers

The way companion objects are created on compilation is by creating a static field inside the surrounding class. It get's instantiated on the static scope (before the test is instantiated).

This is how it looks when decompiled in Java:

public final class SharedPreferencesHelper {
  public static final SharedPreferencesHelper.Companion Companion = new 
  SharedPreferencesHelper.Companion((DefaultConstructorMarker)null);
  // ...
}

For this to work you'll have to assign the given field with your mock instead of intercepting the creation of the Companion object. This doesn't even require to use PowerMock and can be done with reflexion: https://dzone.com/articles/how-to-change-private-static-final-fields

like image 66
pablisco Avatar answered Sep 22 '22 04:09

pablisco