Matt's Tidbits #50 - How to (inadvertently) initialize a Kotlin val twice
Matthew Groves
Posted on January 7, 2020
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:
- 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. - 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 alateinit var
instead of aval
, and also move the call topassMockToSomeStaticObject()
into the@Before
method after we've calledinitMocks()
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 January 7, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
January 7, 2020
October 1, 2019