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() {}
}