반응형

dependencies

dependencies {
    ...
    testImplementation("io.kotest:kotest-runner-junit5:5.7.0")
    testImplementation("io.kotest:kotest-property:5.7.0")
    testImplementation("io.kotest:kotest-assertions-core:5.7.0")
    testImplementation("io.kotest:kotest-extensions-now:5.7.0")
}

tasks.withType<Test> {
    useJUnitPlatform()
}

basic test

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

class DemoTest : FunSpec({
    context("demo") {
        test("1 == 1") {
            "1" shouldBe "1"
        }
    }
})

exception test

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.should
import io.kotest.matchers.string.startWith

class MyTests : StringSpec({
    "test" {
        val exception = shouldThrow<IllegalStateException> {
            throw IllegalStateException("test exception")
        }
        exception.message should startWith("test")
    }
})

ordering test

import io.kotest.core.spec.style.StringSpec
import io.kotest.core.test.TestCaseOrder

class MyTests : StringSpec() {
    // TestCaseOrder.Sequential : 정의된 순서에 맞춰서 실행. 디폴트
    // TestCaseOrder.Random : 랜덤 순서로 실행.
    // TestCaseOrder.Lexicographic : 사전 순으로 실행.
    override fun testCaseOrder(): TestCaseOrder = TestCaseOrder.Sequential

    init {
        "foo" {
            // I run first as I'm defined first
        }

        "bar" {
            // I run second as I'm defined second
        }
    }
}

resource test

import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.StringSpec
import java.io.StringReader

class MyTests : StringSpec({
    isolationMode = IsolationMode.InstancePerLeaf

    val reader = autoClose(StringReader("xyz"))

    "test" {
        println(reader.readText())
    }

    "test2" {
        println(reader.readText())
    }
})

temporary files example

import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.StringSpec
import io.kotest.engine.spec.tempdir
import io.kotest.engine.spec.tempfile

class MyTests : StringSpec({
    isolationMode = IsolationMode.InstancePerLeaf

    val file = tempfile()
    val dir = tempdir()

    "test" {
        println(file.name)
        println(dir.name)
    }

    "test2" {
        println(file.name)
        println(dir.name)
    }
})

ConstantNowTestListener

import io.kotest.core.spec.style.FunSpec
import io.kotest.extensions.time.withConstantNow
import io.kotest.matchers.shouldBe
import java.time.LocalDateTime

class ConstantNowTest : FunSpec({
    test("test") {
        val foreverNow = LocalDateTime.now()

        withConstantNow(foreverNow) {
            LocalDateTime.now() shouldBe foreverNow
        }
    }
})
import io.kotest.core.spec.style.FunSpec
import io.kotest.extensions.time.ConstantNowTestListener
import io.kotest.matchers.shouldBe
import java.time.LocalDateTime

class ConstantNowTest : FunSpec({
    val now = LocalDateTime.now()

    listeners(ConstantNowTestListener(now))

    test("test") {
        LocalDateTime.now() shouldBe now
    }
})

ConstantLocalDateNowTestListener

  • 위 방식으로 LocalDateTime.now()는 mocking할 수 있지만 LocalDate.now()는 할 수 없다.
  • 따라서 LocalDate.now() mocking을 위해 직접 Listener 구현 후 사용.
class ConstantLocalDateNowTestListener(private val now: LocalDate) : TestListener {
    override suspend fun beforeAny(testCase: TestCase) {
        mockkStatic(LocalDate::class)
        every { LocalDate.now() } returns now
    }

    override suspend fun afterAny(testCase: TestCase, result: TestResult) {
        unmockkStatic(LocalDate::class)
    }
}
class ConstantNowTest : FunSpec({
    isolationMode = IsolationMode.InstancePerLeaf

    val now = LocalDate.parse("20240101", DateTimeFormatter.ofPattern("yyyyMMdd"))

    listeners(ConstantLocalDateNowTestListener(now))

    test("test") {
        LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) shouldBe "20240101"
    }
})

fail fast test

import io.kotest.core.spec.style.FunSpec

class FailFastTests : FunSpec({
    // failfast = true : 컨텍스트 하위의 테스트 중 하나가 실패하면 그 이후 실행될 테스트는 skip
    context("context with fail fast enabled").config(failfast = true) {
        test("a") {} // pass
        test("b") { error("boom") } // fail
        test("c") {} // skipped
        context("d") {  // skipped
            test("e") {} // skipped
        }
    }
})

spec level setting

import io.kotest.core.spec.style.FunSpec

class FailFastTests : FunSpec({
    failfast = true
    
    context("context with fail fast enabled") {
        test("a") {} // pass
        test("b") { error("boom") } // fail
        test("c") {} // skipped
        context("d") {  // skipped
            test("e") {} // skipped
        }
    }
})

KotestProjectConfig

예제 코드

class DemoTest1 : FunSpec({
    val uuid = UUID.randomUUID().toString()

    test("test1-a") {
        Thread.sleep(1000)
        println("test1-a-$uuid")
    }

    test("test1-b") {
        Thread.sleep(1000)
        println("test1-b-$uuid")
    }
})
class DemoTest2 : FunSpec({
    val uuid = UUID.randomUUID().toString()

    test("test2-a") {
        Thread.sleep(1000)
        println("test2-a-$uuid")
    }

    test("test2-b") {
        Thread.sleep(1000)
        println("test2-b-$uuid")
    }
})

KotestProjectConfig 설정 전

# 같은 클래스의 uuid가 동일하고 4초동안 실행
# 기본 isolationMode가 SPEC이고, 한 쓰레드에서 테스트가 실행되기 때문
test1-a-f5b4c250-21cf-47a5-b306-36c0058968d5
test1-b-f5b4c250-21cf-47a5-b306-36c0058968d5
test2-a-90fff1c8-b0e5-4654-9eaa-6cdae41e5a98
test2-b-90fff1c8-b0e5-4654-9eaa-6cdae41e5a98

KotestProjectConfig 설정 후

class KotestProjectConfig : AbstractProjectConfig() {
    override val isolationMode = IsolationMode.InstancePerLeaf  // 테스트 모드 글로벌 설정
    override val parallelism = 2                                // 테스트 병렬 처리 수
}
# 같은 클래스여도 uuid가 다르고 2초동안 실행
# 글로벌 설정으로 isolationMode는 LEAF이고, 쓰레드 2개에서 테스트가 실행되기 때문. (테스트별로 쓰레드 하나씩 할당되는듯)
test2-a-11f0fdbb-9698-4f8d-b535-ab4f433fe1eb
test1-a-ab4f9392-e72f-4432-a985-4985dc8db12e
test2-b-9a4eb3b9-88bc-4490-af32-b63da96b6ee0
test1-b-cdbec987-9cd8-439c-b763-d693dabf2e71
반응형

'Development > Kotest' 카테고리의 다른 글

[Kotest] extensions  (0) 2023.11.04
[Kotest] lifecycle hook  (0) 2023.11.04
[Kotest] isolation modes  (0) 2023.11.04
[Kotest] conditional evaluation  (0) 2023.11.04
[Kotest] testing styles  (0) 2023.11.04

+ Recent posts