fbpx

Idiomatic Kotlin in tests: idiomatic stubbings

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 method whenever – 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.when
  • doReturn(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 configuration
  • every{ functionInvocation(argument) } – on every function invocation, equivalent of Mockito.when or Mockito-Kotlin DSL on{}
  • returns value – this is additionally infix function, so we can drop the brackets. Equivalent of .thenReturn(value) or doReturn(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

more insights

Uncategorized
Jarosław Michalik

#kotlinDevChallenge 2023 summary

The KotlinDevChallenge was not just a competition, but a journey of learning, growth, and community engagement. Every participant in this challenge is a winner in

Read More »
KotlinDevChallenge2023
Jarosław Michalik

#kotlinDevChallenge – 24

What do you love most about Kotlin? Productivity increase? Idioms? Or that it isn’t Java? Maybe something else! Share it. It can be short. It

Read More »

AndroidPro newsletter 🚀

join 3057 developers reading AndroidPro newsletter every week

👉 tips & tricks, articles, videos

👉 every Thursday in your inbox

🎁 bonus on signup – Clean Architecture blueprint

brought to you by Jarek Michalik, Kotlin GDE

You can unsubscribe any time. For details visit our privacy policy.

android pro newsletter tips and tricks how to learn android