loading...
Cover image for Matt's Tidbits #50 - How to (inadvertently) initialize a Kotlin val twice

Matt's Tidbits #50 - How to (inadvertently) initialize a Kotlin val twice

mpeng3 profile image Matthew Groves Originally published at Medium ・3 min read

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 @Mock and 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:

  1. The simplest option is to remove the @Mock annotation from line 17 - this will remove the double instantiation, guaranteeing that we're always referring to the same mock instance.
  2. It's also an option to remove the Mockito.mock(MyIfc::class.java) call and just use @Mock instead. However, this option requires some additional refactoring, as we'll need to make this a lateinit var instead of a val, and also move the call to passMockToSomeStaticObject() into the @Before method 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 when and 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.

Posted on by:

mpeng3 profile

Matthew Groves

@mpeng3

Software engineer with 10+ years of professional experience in C++, C#, Java, and Kotlin.

Discussion

markdown guide