void2unit

The Joy of writing helper functions in Kotlin

· Stephan Schröder

Sometimes it feels unreasonably good to take a perfectly fine Java API and lay a tiny veneer of Kotlin on top of it.

A minor inconvenience

I was recently tasked to build a test harness using MockServer around our proxy server written in Kotlin.

MockServer’s Java API

So this is how you could use MockServer’s ClientAndServer class in Java. Since it implements Closeable, I’ll use a try with resources to make sure, the instance is closed once the test is over.

try(var mockServer = ClientAndServer.startClientAndServer(host, port, port)) {
    mockServer.when(
        request()
            .withSecure(isSecure)
            .withMethod("GET")
            .withPath("/view/cart")
            .withCookies(
                cookie("session", "4930456C-C718-476F-971F-CB8E047AB349")
            )
            .withQueryStringParameters(
                param("cartId", "055CA455-1DF7-45BB-8535-4F83E7266092")
            )
        )
        .respond(
            response()
               .withBody("some_response_body")
               .withStatusCode(201)
        );
    
    // let's assume we defined a small helper function to assemble the url of the mockServer
    String mockServerUrl = assembleUrl(isSecure, host, port);
    
    // add the test code here
}

This doesn’t look half bad, and we could probably simply translate this 1-to-1 to Kotlin (try with resource will become use, of course) but there are some tiny details that aren’t very kotliny:

  • the when-method name is just unfortunate, since it’s a keyword in Kotlin
  • the builder pattern is rare in Kotlin code (because of Kotlin’s scope functions), so seeing it here used so much sure would stick out. So I’d use apply whenever I configure request and response.

putting it all together

Instead of handling all these little issues every time we construct a ClientAndServer, let’s write a helper method:

inline fun useMockServer(
    isSecure: Boolean = true,
    host: String = "localhost",
    port: Int = 1080,
    crossinline givenRequest: HttpRequest.() -> Unit,
    crossinline returnResponse: HttpResponse.() -> Unit,
    crossinline test: (mockServerUrl: String) -> Unit,
) {
    /** Using @see[port] as local and remote port in the context of the mock server.
        It seems to work as intended, but I might not understand all the implications.*/
    ClientAndServer.startClientAndServer(host, port, port).use { mockServer: ClientAndServer ->
        mockServer.`when`(
            HttpRequest.request().apply{
                givenRequest()
                // setting this option automatically is more convenient, but a bit magic
                withSecure(isSecure)
            }
        ).respond(HttpResponse.response().apply(returnResponse))
        val mockServerUrl = buildString {
            append(if (isSecure) "https" else "http")
            append("://$host:$port")
        }
        test(mockServerUrl)
    }
}

The amount of non-trivial Kotlin concepts I managed to cram into this helper function is impressive or scary, depending on from where you look at it. We have:

  • inline functions with crossinline lambda parameters with context receivers
  • default values
  • trailing commas
  • and, as you’ll see shortly, we’ll use named parameters to make the code more readable.

Not all the concepts used are necessary. The use of inline is a bit overkill. useMockServer will be used in Unit tests and are therefore not performance critical, but it’s kind of rare to find an opportunity to use crossinline, so I couldn’t resist.

With all of this using our helper method is very concise and “clean”. Even the part’s of the Java Api I continue to use blend right in!

useMockServer(
    isSecure = isSecure,
    host = host,
    port = port,
    givenRequest = {
        withSecure(isSecure)
        withMethod("GET")
        withPath("/view/cart")
        withCookies(
            cookie("session", "4930456C-C718-476F-971F-CB8E047AB349")
        )
        withQueryStringParameters(
            param("cartId", "055CA455-1DF7-45BB-8535-4F83E7266092")
        )
    },
    returnResponse = {
        withBody("some_response_body")
        withStatusCode(201)
    },
) { mockServerUrl ->
    // add the test code here
}

And that’s the joy of writing and using helper functions in Kotlin.