Last week I wrote about how to resolve a bug where the UI of Android Studio's Logcat window is incomplete. This time, I have a word of caution when initializing mocks in Kotlin unit tests.
If you've worked with Mockito before, you've probably written code like this:
In this example, we have an interface that we want to mock, and we're initializing setting it up to return a specific value (5), and we want to verify when we
callStaticObjectToGetNum() that it performs whatever calculations it needs to, but eventually returns the expected mock value to us.
Unfortunately, if you run this test, you will get the following output:
java.lang.AssertionError: Expected: is <5> but: was <0> Expected :is <5> Actual :<0>
What is going on here? The key is on lines 17 & 18. What is happening in this instance is that, despite the
val keyword, our mock is actually getting initialized twice due to having both the
Mockito.mock() calls associated with it.
In short, the
val is assigned one mock instance initially, and that is passed to
passMockToSomeStaticObject(). This happens at class load time. Then, immediately before the test is run, the
MockitoAnnotations.initMocks() call processes the
@Mock associated with this variable and generates a new mock instance. Therefore, when we set up our mock to return 5 for the
getNum() method on line 32, we're setting this on the 2nd instance. However, when we call
callStaticObjectToGetNum() on line 34, it's pointing at the 1st instance - therefore, the test properly reports that the actual value was 0, even though we were expecting 5.
How do we fix this? We have multiple options, and fortunately some of them are easy:
- The simplest option is to remove the
@Mockannotation from line 17 - this will remove the double instantiation, guaranteeing that we're always referring to the same mock instance.
- It's also an option to remove the
Mockito.mock(MyIfc::class.java)call and just use
@Mockinstead. However, this option requires some additional refactoring, as we'll need to make this a
lateinit varinstead of a
val, and also move the call to
@Beforemethod after we've called
initMocks()so we guarantee the "static object" refers to the correct value (and it is non-null).
That's all there is to it! I ran into this about a year ago and it was so simple, and yet so painful to debug that I wanted to share my story and hopefully prevent someone else from falling into the same trap.
Before I wrap up, I have two other things to point out - working with Mockito in Kotlin can be a little difficult as the
is methods are both reserved keywords. There are libraries such as Mockito-kotlin that help rewrite these methods, but you can also achieve the same thing yourself by using the
as keyword and isolating the really ugly "`" syntax to the import statement - see lines 2 and 8 in the example above.
How else would you work around this Mock initialization issue in your code? Leave me a comment below! And, please follow me on Medium if you're interested in being notified of future tidbits.
Interested in joining the awesome team here at Intrepid? We're hiring!
This tidbit was originally delivered on November 2, 2018.