neděle 5. ledna 2020

Kotlin Extendable Tests

Kotlin Extendable Tests

Kotlin Extendable Tests

Imagine, that you have a lot of System/Functional tests. And a lot of these test have some common code. E.g. One code clean your system data. Another starts your testing HttpServer. etc.

This code often end up in some class, lets call it “TestBase”, and all tests inherit from this TestBase. As time goes on this class is bigger and bigger. And a lot of tests, which doesn’t need clean system data are doing so, because this code is part of TestBase.

Solution is split this TestBase somehow. But Splitting it can be quite tricky.

In Groovy(link) this can be easyli done by using Traits (link). And each functionality became own Trait and only tests which need this functionality will implement this trait.

In Kotlin we do not have traits, but it is possilbe to use Interfaces and Delegation to easily implement something with similar capabilities.

Example

You can easily write tests, which use your extensions.

import ... Extendable
import ... ExtendableImpl
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import java.net.HttpURLConnection
import java.net.URL

class ExtensionSampleTest : Extendable by ExtendableImpl() {
    // Here is used our extenssion, which start and stop http server
    val server = register(HttpServerExtension())

    @Test
    fun `test using extension`() {
        // given
        val con = URL("http://localhost:${server.port}/test").openConnection() as HttpURLConnection
        con.setRequestMethod("GET")

        // when
        val response = String(con.inputStream.readAllBytes())

        // then
        assertThat(response).isEqualTo("This is the response")
    }

}

Extension which starts http server. Can look like this:

import com.sun.net.httpserver.HttpServer
import ... Extension
import java.net.InetSocketAddress


class HttpServerExtension(val port: Int = 8000) : Extension {

    lateinit var server: HttpServer

    override fun extBeforeEach() {
        start()
    }

    override fun extAfterEach() {
        stop()
    }

    private fun start() {
        server = HttpServer.create(InetSocketAddress(port), 0).apply {
            createContext("/test") { t ->
                val response = "This is the response"
                t.sendResponseHeaders(200, response.length.toLong())
                t.responseBody.apply {
                    write(response.toByteArray())
                    close()
                }
            }
            start()
        }
    }

    private fun stop() {
        server.stop(1)
    }
    
}

Implementation of extessions for jUnit5

  
import org.junit.jupiter.api.*  
import org.junit.jupiter.api.TestInstance.Lifecycle  
  
/**  
 * Be Aware! Implementing this Interface make the test @TestInstance(Lifecycle.PER_CLASS)
 * It means one test instance is used to run all tests!
 */
@TestInstance(Lifecycle.PER_CLASS)  
interface Extendable {  
  
    val extensions: MutableList<Extension>  
  
    fun <T : Extension> register(extension: T): T {  
        extensions.add(extension)  
        return extension  
    }  
  
    @BeforeAll  
    fun extendableBeforeAll() {  
        extensions.forEach(Extension::extBeforeAll)  
    }  
  
    @BeforeEach  
    fun extendableBeforeEach() {  
        extensions.forEach(Extension::extBeforeEach)  
    }  
  
    @AfterEach  
    fun extendableAfterEach() {  
        extensions.asReversed().forEach(Extension::extAfterEach)  
    }  
  
    @AfterAll  
    fun extendableAfterAll() {  
        extensions.asReversed().forEach(Extension::extAfterAll)  
    }  
}  
  
open class ExtendableImpl : Extendable {  
    override val extensions = mutableListOf<Extension>()  
}  
  
interface Extension {  
    fun extBeforeAll() {}  
    fun extBeforeEach() {}  
    fun extAfterEach() {}  
    fun extAfterAll() {}  
}