Saturday, July 18, 2020

Curious case of MissingMethodInvocationException and Mockito.when

Have you tried to mock a method in Kotlin? As mentioned in this guide we can simply use following code to make it work.

//You can mock concrete classes, not just interfaces 
LinkedList mockedList = mock(LinkedList.class); /
/stubbing 
Mockito.when(mockedList.get(0)).thenReturn("first");


If you try it on kotlin class, it will fail with exception MissingMethodInvocationException.
e.g.
open class DummyPresenter {

fun getSomeValue():String{
return "Some Value"
}
}
@RunWith(MockitoJUnitRunner::class)
class ExampleUnitTest {
@Test
fun testCallGetSomeValue() {
    val presenter: DummyPresenter = mock(DummyPresenter::class.java)
    `when`(presenter.getSomeValue()).thenReturn("Some other value")
    val result = TestClass().callGetSomeValue(presenter)
    println("Test Result:$result")
    assertEquals("Some other value",result)
}
}
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);

Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
   Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.


This exception is misleading and does not explain the cause. It occurs because DummyPresenter.getSomeValue() is not open. 

Obvious solution to this problem is to make the method open, but, is it a good solution? No, why should we mark method open when it is not required.

There are 2 more solutions for unit tests:
1. add a gradle plugin which will do this job for you. Details mentioned in this post.
2. add a file named org.mockito.plugins.MockMaker under test/resources/mockito-extensions. Save the file with mock-maker-inline text.


Mockmaker does not work with Instrumentation tests. For androidTest you have to use gradle plugin specified in option 1 as mentioned below.

    a. add dependancy in project level build.gradle under 
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
}
}
 
    b. create annotation class in some package e.g. com.package as
annotation class AllOpen4Testing
 
    c. add following in module level build.gradle
    apply plugin: "kotlin-allopen"
android {
allOpen {
 annotation("com.package.AllOpen4Testing")
}
}
 
I hope this solves the problem. Thanks for reading.


Android aar deployment in Maven - 2022

Introduction If you are working on android library project, you might be wondering how to publish it on Maven like this . Earl...