Unit testing using Mockito and Kotlin
I am staring to get to grips with Kotlin and I have writted about my challenges in starting a new Kotlin project. The project has been progressing well and hopefully will be published this year. I have tripped over an issue in writing unit tests.
In Java classes are only sealed if you use the keyword final
. By contrast in Kotlin classes are sealed by default and can only be inherited or overridden if we use the keyword open
.
That was perfectly fine while I was writing the code I produced classes like this
1
2
3
4
class LocalBinder : Binder() {
val service: IGpsLoggerService
get() = this@GpsLoggerService
}
However when I was trying to write a test and provide a mock IGpsLoggerService
to the test I ran into problems. I have to say most of the problems revolve around the very poor state of Mockito/Kotlin error messages, I hope this will improve as Kotlin becomes more mainstream in Android development.
In the setup for tests that relied on the GPS service I wrote this
1
2
3
mockservice = mock(IGpsLoggerService::class.java)
mockbinder = mock(GpsLoggerService.LocalBinder::class.java)
Mockito.`when`(mockbinder.service).thenReturn(mockservice)
However when I tried to run this I got this error
org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class com.andrewandderek.trailblazer.service.gpslogger.GpsLoggerService$LocalBinder
Mockito cannot mock/spy because :
- final class
OK, I though that makes sense I need to make the LocalBinder
class open
. I did that but then I got the following error
org.mockito.exceptions.misusing.CannotStubVoidMethodWithReturnValue:
'debug' is a *void method* and it *cannot* be stubbed with a *return value*!
Voids are usually stubbed with Throwables:
doThrow(exception).when(mock).someVoidMethod();
If you need to set the void method to do nothing you can use:
doNothing().when(mock).someVoidMethod();
For more information, check out the javadocs for Mockito.doNothing().
This made no sense at all, there is no method called debug
. I tried to change the property into a method, I tried changing to return type to be a simple Int
but no matter what the method was called or returned I always got the same error. In the end I guessed that the method needed to be open as well. like this
1
2
3
4
open class LocalBinder : Binder() {
open val service: IGpsLoggerService
get() = this@GpsLoggerService
}
Another approach, which is the one I settled on, was to enable the experimental feature in Mockito to allow mocking of final classes. I know this is experimental but it seems to work for my project, I followed the instructions in this blog.