Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cd4781b
Adds TestContainer support in kotest
ashishkujoy Apr 6, 2020
9571cf6
Upgrades kotest to latest 4.0.3
ashishkujoy Apr 15, 2020
b45b02c
Removes usage of ContainerPerSpecListener and ContainerPerSpecListene…
ashishkujoy Apr 17, 2020
ccabb4d
Adds example of ContainerPerSpecListener and ContainerPerSpecListener…
ashishkujoy Apr 17, 2020
9f33df6
Moves kotest examples to docs
ashishkujoy Apr 17, 2020
0d312ce
Removes kotest from docs, renames listener, add extension function fo…
ashishkujoy Apr 19, 2020
2e7bb58
Removes mockk dependency
ashishkujoy Apr 21, 2020
b75fcc3
Removes delay between test and use alpine image for containers
ashishkujoy Apr 21, 2020
636567a
Makes StartablePerSpecListener and StartablePerTestListener as private
ashishkujoy Apr 21, 2020
fbfecb3
Removes private keyword from StartablePerSpecListener and StartablePe…
ashishkujoy Apr 21, 2020
3bfdc06
Replace container based test with mock implementation of startable
ashishkujoy Apr 21, 2020
e9b3a61
Fixes failing test
ashishkujoy Apr 21, 2020
14c514c
Makes StartablePerSpecListener and StartablePerTestListener internal …
ashishkujoy Apr 21, 2020
4d93f7d
Adds support for TestLifecycleAware
ashishkujoy Apr 21, 2020
f03e1c1
Removes TestLifecycleAwareListener and call TestLifecycleAware method…
ashishkujoy Apr 21, 2020
38b6862
Updates doc
ashishkujoy Apr 21, 2020
5572b99
Adds test around test descriptor name
ashishkujoy Apr 21, 2020
439876a
Encode test name to make it filesystemFriendlyName and updates docs
ashishkujoy Apr 21, 2020
2bd5127
Moves TestLifecycleAwareListener in a separate file
ashishkujoy Apr 25, 2020
cf4b30b
Adds TestLifecycleAware support in StartablePerSpecListener
ashishkujoy Apr 25, 2020
64eda9f
Adds a test to verify TestLifecycleAwareListener does not fail when s…
ashishkujoy Apr 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions modules/kotest/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "1.3.71"
id "org.jetbrains.kotlin.kapt" version "1.3.71"
}

ext {
kotestVersion = "4.0.3"
}

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
compile project(':testcontainers')
api("io.kotest:kotest-runner-junit5-jvm:$kotestVersion")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("io.kotest:kotest-assertions-core-jvm:$kotestVersion")
}


test {
useJUnitPlatform()
}

compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
javaParameters = true
}
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.testcontainers.containers

import io.kotest.core.listeners.TestListener
import org.testcontainers.lifecycle.Startable

fun Startable.perTest(): TestListener = StartablePerTestListener(this)
fun Startable.perSpec(): TestListener = StartablePerSpecListener(this)
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.testcontainers.containers

import io.kotest.core.listeners.TestListener
import io.kotest.core.spec.Spec
import io.kotest.core.test.TestCase
import io.kotest.core.test.TestResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.testcontainers.lifecycle.Startable
import org.testcontainers.lifecycle.TestLifecycleAware


/**
* [StartablePerSpecListener] starts the given [startable] before execution of any test in the spec
* and stops after execution of all tests. If the [startable] also inherit from [TestLifecycleAware]
* then its [beforeTest] and [afterTest] method are also called by the listener.
*
* [startable] can any of [GenericContainer] [DockerComposeContainer] [LocalStackContainer] etc.
*
* This listener should be used when you want to use a single container for all tests in a single spec class.
*
* @see
* [StartablePerTestListener]
* */

internal class StartablePerSpecListener(private val startable: Startable) : TestListener {
private val testLifecycleAwareListener = TestLifecycleAwareListener(startable)

override suspend fun beforeSpec(spec: Spec) {
withContext(Dispatchers.IO) {
startable.start()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestLifecycleAware support is still missing...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestLifecycleAware has two methods beforeTest and afterTest, as whereas SpecListener are for runnig before and after the entire spec(Test class). Thats the reason i have not added it there.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please see other integrations

Copy link
Copy Markdown

@sksamuel sksamuel Apr 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ashishkujoy As I understand it, if the startable is a TestLifecycleAware, then we should call beforeTest and afterTest on it, as the container may need to do "other things" between tests, even if we're registering it on the spec (class) level.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation needs to be corrected:
This should be use when you want to a single container for all test in a single test class.
to
This listener should be used when you want to use a single container for all tests in a single spec class.

Copy link
Copy Markdown
Author

@ashishkujoy ashishkujoy Apr 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sksamuel That would be difficult because the before and after test functions in TestLifecylceAware takes TestDescription as parameter which in itself needs testId and testName and these informations are not present in Spec class which we receive in beforeSpec and afterSpec callback.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could store them in a variable in the beforeSpec callback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

support added

Copy link
Copy Markdown
Member

@bsideup bsideup May 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ashishkujoy please do not resolve conversations started by others. e.g. this one remains unresolved, in fact.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be difficult because the before and after test functions in TestLifecylceAware takes TestDescription as parameter which in itself needs testId and testName and these informations are not present in Spec class which we receive in beforeSpec and afterSpec callback.

something to improve in kotest? Other test frameworks don't have this problem.

}
}

override suspend fun beforeTest(testCase: TestCase) {
withContext(Dispatchers.IO) {
testLifecycleAwareListener.beforeTest(testCase)
}
}

override suspend fun afterSpec(spec: Spec) {
withContext(Dispatchers.IO) {
startable.stop()
}
}

override suspend fun afterTest(testCase: TestCase, result: TestResult) {
withContext(Dispatchers.IO) {
testLifecycleAwareListener.afterTest(testCase, result)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.testcontainers.containers

import io.kotest.core.listeners.TestListener
import io.kotest.core.test.TestCase
import io.kotest.core.test.TestResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.testcontainers.lifecycle.Startable
import org.testcontainers.lifecycle.TestLifecycleAware


/**
* [StartablePerTestListener] starts the given [startable] before execution of each test in the spec
* and stops after execution of each test. If the [startable] also inherit from [TestLifecycleAware]
* then its [beforeTest] and [afterTest] method are also called by the listener.
*
* [startable] can any of [GenericContainer] [DockerComposeContainer] [LocalStackContainer] etc.
*
* This should be used when you want a fresh container for each test.
*
* @see[StartablePerSpecListener]
* */
internal class StartablePerTestListener(private val startable: Startable) : TestListener {
private val testLifecycleAwareListener = TestLifecycleAwareListener(startable)

override suspend fun beforeTest(testCase: TestCase) {
withContext(Dispatchers.IO) {
testLifecycleAwareListener.beforeTest(testCase)
startable.start()
}
}

override suspend fun afterTest(testCase: TestCase, result: TestResult) {
withContext(Dispatchers.IO) {
testLifecycleAwareListener.afterTest(testCase, result)
startable.stop()
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.testcontainers.containers

import io.kotest.core.listeners.TestListener
import io.kotest.core.test.TestCase
import io.kotest.core.test.TestResult
import org.testcontainers.lifecycle.Startable
import org.testcontainers.lifecycle.TestDescription
import org.testcontainers.lifecycle.TestLifecycleAware
import java.net.URLEncoder
import java.util.*

internal class TestLifecycleAwareListener(startable: Startable) : TestListener {
private val testLifecycleAware = startable as? TestLifecycleAware

override suspend fun beforeTest(testCase: TestCase) {
testLifecycleAware?.beforeTest(testCase.toTestDescription())
}

override suspend fun afterTest(testCase: TestCase, result: TestResult) {
testLifecycleAware?.afterTest(testCase.toTestDescription(), Optional.ofNullable(result.error))
}
}

private fun TestCase.toTestDescription() = object : TestDescription {

override fun getFilesystemFriendlyName(): String {
return URLEncoder.encode(name, "UTF-8")
}

override fun getTestId(): String {
return description.id()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.testcontainers.containers

import org.testcontainers.lifecycle.TestDescription
import org.testcontainers.lifecycle.TestLifecycleAware
import java.util.*

internal class StartableTestLifecycleAware : TestStartable(), TestLifecycleAware {
val testDescriptions = mutableListOf<TestDescription?>()

override fun beforeTest(description: TestDescription?) {
testDescriptions.add(description)
}

override fun afterTest(description: TestDescription?, throwable: Optional<Throwable>?) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.testcontainers.containers

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.collections.shouldHaveSize

class StartableTestLifecycleAwareTest : StringSpec({
val startableTestLifecycleAwareForPerTest = StartableTestLifecycleAware()
val startableTestLifecycleAwareForPerSpec = StartableTestLifecycleAware()

listeners(startableTestLifecycleAwareForPerTest.perTest(), startableTestLifecycleAwareForPerSpec.perSpec())

"beforeTestCount for first test should be one" {
startableTestLifecycleAwareForPerTest.testDescriptions shouldHaveSize 1
startableTestLifecycleAwareForPerSpec.testDescriptions shouldHaveSize 1
}

"beforeTestCount for second test should be two" {
startableTestLifecycleAwareForPerTest.testDescriptions shouldHaveSize 2
startableTestLifecycleAwareForPerTest.testDescriptions shouldHaveSize 2
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.testcontainers.containers

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe

class TestContainerKotestIntegrationPerTest : StringSpec({
val testStartable = TestStartable()
listeners(testStartable.perTest())

"start count for first test should be one" {
testStartable.startCount shouldBe 1
}

"start count for second test should be two" {
testStartable.startCount shouldBe 2
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.testcontainers.containers

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe

class TestLifecycleAwareListenerTest : StringSpec({
val startableTestLifecycleAware = StartableTestLifecycleAware()
val startable = TestStartable()
val testLifecycleAwareListener = TestLifecycleAwareListener(startableTestLifecycleAware)
val anotherTestLifecycleAwareListener = TestLifecycleAwareListener(startable)

listeners(testLifecycleAwareListener, anotherTestLifecycleAwareListener)

"it should not break for listener having startable which is not of type testLifecycleAware" {
startable.startCount shouldBe 0
}

"test id in test description should be combination of test name and package name" {
val testDescription = startableTestLifecycleAware.testDescriptions[1]
testDescription?.testId shouldBe "org.testcontainers.containers.TestLifecycleAwareListenerTest/test id in test" +
" description should be combination of test name and package name"
}

"fileSystemFriendlyName .. in /// test description should be encoded test name" {
val testDescription = startableTestLifecycleAware.testDescriptions[2]
val encodedTestName = "fileSystemFriendlyName+..+in+%2F%2F%2F+test+description+should+be+encoded+test+name"

testDescription?.filesystemFriendlyName shouldBe encodedTestName
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.testcontainers.containers

import org.testcontainers.lifecycle.Startable

internal open class TestStartable : Startable {
var startCount = 0
override fun start() {
startCount++
}
override fun stop() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.testcontainers.containers

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe

class TestcontainerKotestIntegrationPerSpec : StringSpec({
val testStartable = TestStartable()
listeners(testStartable.perSpec())

"start count for first test should be one" {
testStartable.startCount shouldBe 1
}

"start count for second test should also be one" {
testStartable.startCount shouldBe 1
}
})