Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking companion object function in kotlin

I'm using PowerMock and Roboelectric and would like to mock a companion object function for a class. When I do this I get an error:

org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

What I have is basically this:

open class MockableClass private constructor(context: Context) {

    companion object {

        private val INSTANCE_LOCK = Any()
        private var sInstance: MockableClass? = null

        @JvmStatic
        fun getInstance(context: Context): MockableClass? {
            synchronized(INSTANCE_LOCK) {
                sInstance = (sInstance ?: MockableClass(context).let {
                    if (it.isValid) it
                    else null
                }
            }
            return sInstance
        }
    }

    init {
        // Do some initialization using context...
        // Set isValid to true/false depending on success
    }

    val isValid: Boolean
}

When I go to test this I will assume it will always work and would like to have getInstance just return a mocked version of the MockableClass.

@RunWith(RobolectricTestRunner::class)
@Config(manifest = "src/main/AndroidManifest.xml",
        constants = BuildConfig::class,
        sdk = intArrayOf(23))
@PowerMockIgnore("org.mockito.*", "org.robolectric.*", "android.*")
@PrepareForTest(MockableClass::class)
class MyTest {

    private lateinit var context: Context

    @get:Rule
    val rule = PowerMockRule()

    @Before
    fun setUp() {
        context = RuntimeEnvironment.application as Context
    }

    @Test
    fun test() {
        val instance = mock(MockableClass::class.java)
        mockStatic(MockableClass::class.java)
        `when`(MockableClass.getInstance(Matchers.isA(Context::class.java)))
                .thenReturn(instance)
        assertEquals(instance,
                MockableClass.getInstance(RuntimeEnvironment.application as Context))
    }
}

I've also tried mocking MockableClass.Companion::class.java with no luck.

Does anyone know what I need to do to be able to mock out this getInstance function?

like image 537
TheHebrewHammer Avatar asked Mar 17 '17 15:03

TheHebrewHammer


People also ask

How do you use the companion object in Kotlin?

To create a companion object, you need to add the companion keyword in front of the object declaration. The output of the above code is “ You are calling me :) ” This is all about the companion object in Kotlin. Hope you liked the blog and will use the concept of companion in your Android application.

How do you mock a method in Kotlin?

mockk:mockk supports to mock top-level functions in Kotlin. Similar to extension functions, for top-level functions, Kotlin creates a class containing static functions under the hood as well, which in turn can be mocked.

How do you mock a static method in Mockk?

Mocking static methodsRather than passing a reference to the class, you pass the class name as a string. You can also choose to pass in a reference to the class, and MockK will figure out the class name. Like object mocks, static mocks behave like spies. The real method will be called if the method is not stubbed.

What is the difference between companion object and object in Kotlin?

Object expressions are executed (and initialized) immediately, where they are used. Object declarations are initialized lazily, when accessed for the first time. A companion object is initialized when the corresponding class is loaded (resolved) that matches the semantics of a Java static initializer.


1 Answers

What I ended up doing...

Instead of mocking the static method I ended up using PowerMockito's whenNew function to return a mocked instance of MockableClass. The final code looks something like this:

@RunWith(RobolectricTestRunner::class)
@Config(manifest = "src/main/AndroidManifest.xml",
        constants = BuildConfig::class,
        sdk = intArrayOf(23))
@PowerMockIgnore("org.mockito.*", "org.robolectric.*", "android.*")
@PrepareForTest(MockableClass::class)
class MyTest {

    private lateinit var context: Context

    @JvmField
    @Rule
    var rule = PowerMockRule()

    @Before
    fun setUp() {
        context = RuntimeEnvironment.application as Context
    }

    @Test
    fun test() {
        val instance = mock(MockableClass::class.java)
        instance.isValid = true
        whenNew<MockableClass>("com.example.MockableClass")
                .withAnyArguments()
                .thenReturn(instance)
        assertEquals(instance, MockableClass.getInstance(context))
    }
}
like image 142
TheHebrewHammer Avatar answered Sep 20 '22 15:09

TheHebrewHammer