Stub is a type of test double, which is a generic term for objects that stand in the place of other objects in a test scenario.
💡 We use stubs for returning predefined values
Such objects are used to help with testing and provide a guarantee that the code being tested will behave in a certain way.
The most common types of test doubles are
- mocks
- fakes
- stubs
- spies
💡Very often we refer to all test doubles as “mocks”
In this article you will learn several ways of creating stubs… using Kotlin idioms.
A stub is a test double that returns predetermined values instead of invoking real methods.
They are used to control the flow of a test and allow a test to run in isolation.
In Kotlin, stubs can be created with libraries such as Mockito-Kotlin and MockK, or by using a fun interface.
We will work on the following piece of code:
interface ResolveUser {
fun resolve(id: String): UserDetails
}
data class UserDetails(val id: String, val name: String)
And first… let’s see the old way of creating stubs with Mockito – Java style.
Stub with Mockito – Java style
In ResolveCurrentUserUseCase
interface, you could create a stub like this:
val useCase: ResolveUser = Mockito.mock(ResolveUser::class.java)
Mockito.`when`(useCase.resolve("123")).thenReturn(UserDetails("123", "Jarek"))
println(useCase.resolve("123")) //UserDetails(id=123, name=Jarek)
This stub would return a User
object with an ID of 123
and a name of Jarek
whenever the resolve
method is called with the parameter 123
.
This code does not look very kotlinish
- we are referencing
::class.java
– maybe we could use reifed function? Mockito.`when`
is in backticks – when is keyword in Kotlin- method chaining (when(x.method).thenReturn) may become unreadable, so maybe we could use some labda-based DSL for that?
Let’s see how this stub could look like, if we introduce wrapper for Mockito – Mockito-Kotlin
Stub with Mockito-Kotlin
To your module/build.gradle add:
testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"
After syncing project now we can use Kotlin wrappers for Mockito:
val useCase: ResolveUser = mock()
whenever(useCase.resolve("123")).thenReturn(UserDetails("123", "Jarek"))
println(useCase.resolve("123")) //UserDetails(id=123, name=Jarek)
This stub works the same, however we have some improvements:
mock()
method is reified – compiler will know the type of ResolveUser, so we don’t need to pass any::class.java
argument- instead of
Mockito.`when`
we are using inline methodwhenever
– it’s just a wrapper for method with backticks
inline fun <T> whenever(methodCall: T): OngoingStubbing<T> {
return Mockito.`when`(methodCall)!!
}
We still have method chaining, so let’s fix that!
To improve readability a little bit, we can join mock declaration and configuration into one expression:
val useCase: ResolveUser = mock {
on { resolve("123") } doReturn UserDetails("123", "Jarek")
}
println(useCase.resolve("123")) //UserDetails(id=123, name=Jarek)
mock
function in Mockito-Kotlin accepts lambda KStubbing<T>.(T) -> Unit
as the last argument, meaning we can configure stubbing with DSL functions.
on{functionInvocation(argument)}
– equivalent of Mockito.whendoReturn(value)
– equivalent of .thenReturn
Stub with MockK
Mockito was designed to work with Java, and Mockito-Kotlin introduces set of useful functions, however it still lacks some Kotlin-specific functions such as stubbing suspend functions.
To add MockK to your project, add following dependency:
testImplementation 'io.mockk:mockk:1.13.2'
After project sync, we can start working with MockK.
val useCase: ResolveUser = mockk {
every { resolve("123") } returns UserDetails("123", "Jarek")
}
println(useCase.resolve("123")) //UserDetails(id=123, name=Jarek)
mockk{}
– we put in there functions with stubbing configurationevery{ functionInvocation(argument) }
– on every function invocation, equivalent of Mockito.when or Mockito-Kotlin DSLon{}
returns value
– this is additionally infix function, so we can drop the brackets. Equivalent of.thenReturn(value)
ordoReturn(value)
To learn how to stub and mock suspend functions with MockK, refer to this article
Stub with fun interface
But wait… do we really need mocking frameworks for configuring simple stubbings? The answer is no.
Let’s consider anonymous interface implementation for ResolveUser
:
val useCase: ResolveUser = object : ResolveUser {
override fun resolve(id: String): UserDetails {
return when (id) {
"123" -> UserDetails("123", "Jarek")
else -> throw NotImplementedError("no stubbing for $id")
}
}
}
println(useCase.resolve("123")) //UserDetails(id=123, name=Jarek)
ResolveUser is implemented as object : Type{}
. when
expression returns value for id=”123″ and throws error in else block.
We can make it a little shorter, if we add fun
to our interface:
fun interface ResolveUser {
fun resolve(id: String): UserDetails
}
data class UserDetails(val id: String, val name: String)
And now the stubbing may look like this:
val useCase = ResolveUser { id ->
when (id) {
"123" -> UserDetails("123", "Jarek")
else -> throw NotImplementedError("no stubbing for $id")
}
}
println(useCase.resolve("123")) //UserDetails(id=123, name=Jarek)
We can make it even shorter, by ommiting the id value check:
val useCase = ResolveUser { UserDetails("123", "Jarek") }
println(useCase.resolve("123")) //UserDetails(id=123, name=Jarek)
println(useCase.resolve("124")) //UserDetails(id=123, name=Jarek)
println(useCase.resolve("125")) //UserDetails(id=123, name=Jarek)
With ommiting the value check, we won’t have the same behaviour as in strict stub framework configuration. However, for simple test cases it is absolutely sufficient.
Summary
In this article we learned several ways of creating and configuring stubs with Kotlin idioms:
- reification for Mockito stub declaration
- inline wrapper functions for problematic method names
- DSL configuration instead of method chaining
- infix functions for increasing readability of stub configuration
- fun interface usage for shortening anonymous interface implementation