For a couple of last years, I saw issues in our processes and in the way we work. I managed to do some changes and change how people think about testing.
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 byExtendableImpl(){// Here is used our extenssion, which start and stop http serverval server =register(HttpServerExtension())@Testfun `test using extension`(){// givenval con =URL("http://localhost:${server.port}/test").openConnection()as HttpURLConnection
con.setRequestMethod("GET")// whenval response =String(con.inputStream.readAllBytes())// thenassertThat(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
classHttpServerExtension(val port: Int =8000): Extension {lateinitvar server: HttpServer
overridefunextBeforeEach(){start()}overridefunextAfterEach(){stop()}privatefunstart(){
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()}}privatefunstop(){
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
}@BeforeAllfunextendableBeforeAll(){
extensions.forEach(Extension::extBeforeAll)}@BeforeEachfunextendableBeforeEach(){
extensions.forEach(Extension::extBeforeEach)}@AfterEachfunextendableAfterEach(){
extensions.asReversed().forEach(Extension::extAfterEach)}@AfterAllfunextendableAfterAll(){
extensions.asReversed().forEach(Extension::extAfterAll)}}openclass ExtendableImpl : Extendable {overrideval extensions = mutableListOf<Extension>()}interface Extension {funextBeforeAll(){}funextBeforeEach(){}funextAfterEach(){}funextAfterAll(){}}