Home » Cut back Boilerplate When Working Kotlin Tests | by Uzi Landsmann | Jun, 2023

Cut back Boilerplate When Working Kotlin Tests | by Uzi Landsmann | Jun, 2023

by Icecream
0 comment

Using a nifty Kotlin trick, you possibly can hold your assessments clear and straightforward to grasp and keep

Image generated utilizing DiffusionBee

Testing ought to be straightforward. If your assessments are too advanced and tough to take care of, they lose that means. Tests ought to provide help to perceive your software logic and ensure that it does what it claims, so conserving your assessments easy is a key level in making them helpful for your self and others who should take care of them later. One manner to take action is to get rid of the boilerplate that may litter your assessments. This article will take a look at one approach to obtain simply that.

Scenario

Suppose you’ve got an software that sends stuff to some receiving API. Here is the stuff we’re sending:

information class Stuff(val identify: String, val kind: String)

A service forwards the request to another class for the precise sending. Here’s what the code seems to be like:

import okhttp3.OkHttpClient

class StuffService() {
non-public val consumer = OkHttpClient()
.newBuilder()
.addInterceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("Content-Type", "software/json")
.construct()
chain.proceed(request)
}.construct()

non-public val stuffLink = StuffHyperlink(consumer, "http://some.the place")

enjoyable shipStuff(stuff: Stuff) = stuffLink.shipStuff(stuff)
}

The consumer (we name it a hyperlink to keep away from having too many issues referred to as a consumer) does the precise sending by making a request and utilizing the given okhttp consumer and URL to ship it. The code seems to be like this:

import com.fasterxml.jackson.databind.ObjectMapper
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestPhysique.Companion.toRequestPhysique
import okhttp3.Response

class StuffHyperlink(non-public val consumer: OkHttpClient, non-public val url: String) {
non-public val objectMapper = ObjectMapper()

enjoyable shipStuff(stuff: Stuff): Response {
val request = Request.Builder()
.url(url)
.put up(
objectMapper.writeValueAsString(stuff)
.toRequestPhysique("software/json".toMediaType()),
).construct()

return consumer.newCall(request).execute()
}
}

Test model 1

Now, suppose we need to check that the proper values are despatched to the receiving API utilizing the StuffHyperlink class. Here’s our first try:

    @Test
enjoyable `Stuff ought to be despatched by StuffHyperlink (model 1)`() {
val requestSlot = slot<Request>()
val mockkClient = mockk<OkHttpClient> {
each { newCall(seize(requestSlot)) } returns mockk(relaxed = true)
}

val stuffLink = StuffHyperlink(mockkClient, url)
val bob = Stuff("Bob", "Armchair")
stuffLink.shipStuff(bob)
val request = requestSlot.captured
val bodyAsMap = parseRequestPhysique(request)

assertThat(request.url.toString()).isEqualTo(url)
assertThat(bodyAsMap["name"]).isEqualTo("Bob")
assertThat(bodyAsMap["type"]).isEqualTo("Armchair")
}

About half of this methodology is boilerplate. We’re making a slot as a placeholder for the request, a mock consumer instrumented with the slot, an occasion of the StuffHyperlink class to run the assessments on, after which name the ship methodology with the check information.

Only after that is performed can we do what we meant: test the request accommodates what we count on it to.

Test model 2

Realizing that that is cumbersome, as we don’t need to repeat this follow in every check methodology we intend to write down, we would attempt to put a number of the preparation logic in a reusable methodology, as proven beneath:

    @Test
enjoyable `Stuff ought to be despatched by StuffHyperlink (model 2)`() {
val (mockkClient, requestSlot) = createMockAndSlot()
val stuffLink = StuffHyperlink(mockkClient, url)
val bob = Stuff("Bob", "Armchair")
stuffLink.shipStuff(bob)
val request = requestSlot.captured
val bodyAsMap = parseRequestPhysique(request)

assertThat(request.url.toString()).isEqualTo(url)
assertThat(bodyAsMap["name"]).isEqualTo("Bob")
assertThat(bodyAsMap["type"]).isEqualTo("Armchair")
}

non-public enjoyable createMockAndSlot(): Pair<OkHttpClient, CapturingSlot<Request>> {
val requestSlot = slot<Request>()
val mockkClient = mockk<OkHttpClient> {
each<Call> { newCall(seize(requestSlot)) } returns mockk<Call>(relaxed = true)
}
return mockkClient to requestSlot
}

Test model 3

Still, some boilerplate stays, and the reusable methodology just isn’t too lovely with its unusual identify and double return worth. What we wish is a approach to keep away from all of it. Here’s the third try:

    @Test
enjoyable `Stuff ought to be despatched by StuffHyperlink (model 3)`() {
val bob = Stuff("Bob", "Armchair")
val request = runInStuffHyperlink { shipStuff(bob) }
val bodyAsMap = parseRequestPhysique(request)

assertThat(request.url.toString()).isEqualTo(url)
assertThat(bodyAsMap["name"]).isEqualTo("Bob")
assertThat(bodyAsMap["type"]).isEqualTo("Armchair")
}

non-public enjoyable runInStuffHyperlink(motion: StuffHyperlink.() -> Unit): Request {
val requestSlot = slot<Request>()
val mockkClient = mockk<OkHttpClient> {
each<Call> { newCall(seize(requestSlot)) } returns mockk<Call>(relaxed = true)
}
val stuffLink = StuffHyperlink(mockkClient, url)
stuffLink.motion()
return requestSlot.captured
}

The reusable runInStuffHyperlink methodology lets us do away with the remainder of the boilerplate. It takes as an argument a technique to run on the StuffHyperlink class. The implementation creates the slot and the consumer, then creates an occasion of the StuffHyperlink class, and eventually runs the given methodology on that immediate and returns the captured slot.

What form of black magic is that this?

This approach above is defined within the Kotlin Function literals with receiver documentation. It is commonly used to design DSLs, as defined intimately within the Programming DSLs in Kotlin — by Venkat Subramaniam sequence, see for instance the Design for Separate Implicit Contexts part.

You may also like

Leave a Comment