Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extra scopes for BehaviorSpec #593

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions doc/styles.md
Expand Up @@ -114,6 +114,26 @@ class MyTests : BehaviorSpec({
Because `when` is a keyword in Kotlin, we must enclose with backticks. Alternatively, there are title case versions
available if you don't like the use of backticks, eg, `Given`, `When`, `Then`.

You can also use the `And` keyword in `Given` and `When` to add an extra depth to it:

```kotlin
class MyTests : BehaviorSpec({
given("a broomstick") {
and("a witch") {
`when`("The witch sits on it") {
and("she laughs hysterically") {
then("She should be able to fly") {
}
}
}
}
}
})

```

Note: `Then` scope doesn't have an `and` scope due to a gradle bug. For more information, see #594

### Free Spec

`FreeSpec` allows you to nest arbitary levels of depth using the keyword `-` (minus), as such:
Expand Down
Expand Up @@ -15,48 +15,129 @@ abstract class AbstractBehaviorSpec(body: AbstractBehaviorSpec.() -> Unit = {})
body()
}

fun Given(name: String, test: suspend GivenContext.() -> Unit) = addGivenContext(name, test)
fun given(name: String, test: suspend GivenContext.() -> Unit) = addGivenContext(name, test)

private fun addGivenContext(name: String, test: suspend GivenContext.() -> Unit) {
addTestCase("Given: $name", { thisSpec.GivenContext(this).test() }, defaultTestCaseConfig, TestType.Container)
}

@KotlinTestDsl
inner class GivenContext(val context: TestContext) {
suspend fun And(name: String, test: suspend WhenContext.() -> Unit) = and(name, test)
suspend fun and(name: String, test: suspend WhenContext.() -> Unit) = add("And: $name", test)
suspend fun When(name: String, test: suspend WhenContext.() -> Unit) = `when`(name, test)
suspend fun `when`(name: String, test: suspend WhenContext.() -> Unit) = add("When: $name", test)
private suspend fun add(name: String, test: suspend WhenContext.() -> Unit) =
context.registerTestCase(name, this@AbstractBehaviorSpec, { this@AbstractBehaviorSpec.WhenContext(this).test() }, this@AbstractBehaviorSpec.defaultTestCaseConfig, TestType.Container)
suspend fun And(name: String, test: suspend GivenAndContext.() -> Unit) = addAndContext(name, test)
suspend fun and(name: String, test: suspend GivenAndContext.() -> Unit) = addAndContext(name, test)

private suspend fun addAndContext(name: String, test: suspend GivenAndContext.() -> Unit) {
context.registerTestCase("And: $name", thisSpec, { thisSpec.GivenAndContext(this).test() }, thisSpec.defaultTestCaseConfig, TestType.Container)
}

suspend fun When(name: String, test: suspend WhenContext.() -> Unit) = addWhenContext(name, test)
suspend fun `when`(name: String, test: suspend WhenContext.() -> Unit) = addWhenContext(name, test)

private suspend fun addWhenContext(name: String, test: suspend WhenContext.() -> Unit) {
context.registerTestCase("When: $name", thisSpec, { thisSpec.WhenContext(this).test() }, thisSpec.defaultTestCaseConfig, TestType.Container)
}

suspend fun Then(name: String, test: suspend ThenContext.() -> Unit) = addThenContext(name, test)
suspend fun then(name: String, test: suspend ThenContext.() -> Unit) = addThenContext(name, test)

private suspend fun addThenContext(name: String, test: suspend ThenContext.() -> Unit) {
context.registerTestCase("Then: $name", thisSpec, { thisSpec.ThenContext(this).test() }, thisSpec.defaultTestCaseConfig, TestType.Container)
}

fun then(name: String) = TestScope(name, context)
}

@KotlinTestDsl
inner class GivenAndContext(val context: TestContext) {
suspend fun And(name: String, test: suspend GivenAndContext.() -> Unit) = addAndContext(name, test)
suspend fun and(name: String, test: suspend GivenAndContext.() -> Unit) = addAndContext(name, test)

private suspend fun addAndContext(name: String, test: suspend GivenAndContext.() -> Unit) {
context.registerTestCase("And: $name", thisSpec, { thisSpec.GivenAndContext(this).test() }, thisSpec.defaultTestCaseConfig, TestType.Container)
}

suspend fun When(name: String, test: suspend WhenContext.() -> Unit) = addWhenContext(name, test)
suspend fun `when`(name: String, test: suspend WhenContext.() -> Unit) = addWhenContext(name, test)

private suspend fun addWhenContext(name: String, test: suspend WhenContext.() -> Unit) {
context.registerTestCase("When: $name", thisSpec, { thisSpec.WhenContext(this).test() }, thisSpec.defaultTestCaseConfig, TestType.Container)
}

suspend fun Then(name: String, test: suspend ThenContext.() -> Unit) = addThenContext(name, test)
suspend fun then(name: String, test: suspend ThenContext.() -> Unit) = addThenContext(name, test)

private suspend fun addThenContext(name: String, test: suspend ThenContext.() -> Unit) {
context.registerTestCase("Then: $name", thisSpec, { thisSpec.ThenContext(this).test() }, thisSpec.defaultTestCaseConfig, TestType.Container)
}

fun then(name: String) = TestScope(name, context)
}

@KotlinTestDsl
inner class WhenContext(val context: TestContext) {
suspend fun Then(name: String, test: suspend TestContext.() -> Unit) = then(name, test)
suspend fun then(name: String, test: suspend TestContext.() -> Unit) =
context.registerTestCase("Then: $name", this@AbstractBehaviorSpec, test, this@AbstractBehaviorSpec.defaultTestCaseConfig, TestType.Test)
suspend fun And(name: String, test: suspend WhenAndContext.() -> Unit) = addAndContext(name, test)
suspend fun and(name: String, test: suspend WhenAndContext.() -> Unit) = addAndContext(name, test)

private suspend fun addAndContext(name: String, test: suspend WhenAndContext.() -> Unit) {
context.registerTestCase("And: $name", thisSpec, { thisSpec.WhenAndContext(this).test() }, thisSpec.defaultTestCaseConfig, TestType.Container)
}

suspend fun Then(name: String, test: suspend ThenContext.() -> Unit) = addThenContext(name, test)
suspend fun then(name: String, test: suspend ThenContext.() -> Unit) = addThenContext(name, test)

private suspend fun addThenContext(name: String, test: suspend ThenContext.() -> Unit) {
context.registerTestCase("Then: $name", thisSpec, { thisSpec.ThenContext(this).test() }, thisSpec.defaultTestCaseConfig, TestType.Test)
}

fun then(name: String) = TestScope(name, context)
}

fun then(name: String): ThenScope = ThenScope(name, context)
@KotlinTestDsl
inner class WhenAndContext(val context: TestContext) {
suspend fun And(name: String, test: suspend WhenAndContext.() -> Unit) = addAndContext(name, test)
suspend fun and(name: String, test: suspend WhenAndContext.() -> Unit) = addAndContext(name, test)

private suspend fun addAndContext(name: String, test: suspend WhenAndContext.() -> Unit) {
context.registerTestCase("And: $name", thisSpec, { thisSpec.WhenAndContext(this).test() }, thisSpec.defaultTestCaseConfig, TestType.Container)
}

suspend fun Then(name: String, test: suspend ThenContext.() -> Unit) = addThenContext(name, test)
suspend fun then(name: String, test: suspend ThenContext.() -> Unit) = addThenContext(name, test)

private suspend fun addThenContext(name: String, test: suspend ThenContext.() -> Unit) {
context.registerTestCase("Then: $name", thisSpec, { thisSpec.ThenContext(this).test() }, thisSpec.defaultTestCaseConfig, TestType.Test)
}

fun then(name: String) = TestScope(name, context)
}

@KotlinTestDsl
inner class ThenScope(val name: String, val context: TestContext) {
inner class ThenContext(val context: TestContext)

@KotlinTestDsl
inner class TestScope(val name: String, val context: TestContext) {
suspend fun config(
invocations: Int? = null,
enabled: Boolean? = null,
timeout: Duration? = null,
threads: Int? = null,
tags: Set<Tag>? = null,
extensions: List<TestCaseExtension>? = null,
test: TestContext.() -> Unit) {
invocations: Int? = null,
enabled: Boolean? = null,
timeout: Duration? = null,
threads: Int? = null,
tags: Set<Tag>? = null,
extensions: List<TestCaseExtension>? = null,
test: TestContext.() -> Unit) {
val config = TestCaseConfig(
enabled ?: this@AbstractBehaviorSpec.defaultTestCaseConfig.enabled,
invocations ?: this@AbstractBehaviorSpec.defaultTestCaseConfig.invocations,
timeout ?: this@AbstractBehaviorSpec.defaultTestCaseConfig.timeout,
threads ?: this@AbstractBehaviorSpec.defaultTestCaseConfig.threads,
tags ?: this@AbstractBehaviorSpec.defaultTestCaseConfig.tags,
extensions ?: this@AbstractBehaviorSpec.defaultTestCaseConfig.extensions)
enabled ?: this@AbstractBehaviorSpec.defaultTestCaseConfig.enabled,
invocations ?: this@AbstractBehaviorSpec.defaultTestCaseConfig.invocations,
timeout ?: this@AbstractBehaviorSpec.defaultTestCaseConfig.timeout,
threads ?: this@AbstractBehaviorSpec.defaultTestCaseConfig.threads,
tags ?: this@AbstractBehaviorSpec.defaultTestCaseConfig.tags,
extensions ?: this@AbstractBehaviorSpec.defaultTestCaseConfig.extensions)

context.registerTestCase(name, this@AbstractBehaviorSpec, { test.invoke(this) }, config, TestType.Test)
}
}

fun Given(name: String, test: suspend GivenContext.() -> Unit) = given(name, test)
fun given(name: String, test: suspend GivenContext.() -> Unit) =
addTestCase("Given: $name", { this@AbstractBehaviorSpec.GivenContext(this).test() }, defaultTestCaseConfig, TestType.Container)
private val thisSpec: AbstractBehaviorSpec
get() = this@AbstractBehaviorSpec

}
@@ -1,12 +1,84 @@
package com.sksamuel.kotlintest.specs.behavior

import io.kotlintest.Spec
import io.kotlintest.TestCase
import io.kotlintest.TestResult
import io.kotlintest.matchers.numerics.shouldBeLessThan
import io.kotlintest.matchers.string.shouldStartWith
import io.kotlintest.shouldBe
import io.kotlintest.specs.BehaviorSpec
import java.util.concurrent.atomic.AtomicInteger

class BehaviorSpecTest : BehaviorSpec() {

private val counter = AtomicInteger(0)

init {
Given("The string foo") {
val foo = "foo"

And("The string jar") {
val jar = "jar"

When("I add them together") {
val together = foo + jar

Then("It should be foojar") {
together shouldBe "foojar"
}
}
}

And("The string bar") {
val bar = "bar"

And("The string fuz") {
val fuz = "fuz"

When("I add them together") {
val together = foo + bar + fuz

And("Count their length") {
val length = together.length

And("Sum one to it") {
val sum = length + 1

Then("It should be 10") {
sum shouldBe 10
}
}

Then("It should be 9") {
length shouldBe 9
}
}

Then("It should be foobarfuz") {
together shouldBe "foobarfuz"
}

And("I make it uppercase") {
val upper = together.toUpperCase()

Then("It should be FOOBARFUZ") {
upper shouldBe "FOOBARFUZ"
}

Then("It should start with F") {
upper shouldStartWith "F"
}
}
}
}
}

Then("It should be foo") {
foo shouldBe "foo"
}
}


given("a") {
`when`("b") {
then("c") {
Expand All @@ -19,5 +91,39 @@ class BehaviorSpecTest : BehaviorSpec() {
counter.get() shouldBe 3
}
}

given("A") {

counter.incrementAndGet()

and("b") {

counter.incrementAndGet()

and("c") {

counter.incrementAndGet()

`when`("d") {

counter.incrementAndGet()

and("e") {

counter.incrementAndGet()

then("f") {

counter.incrementAndGet()
}
}
}
}
}
}
}

override fun afterSpecClass(spec: Spec, results: Map<TestCase, TestResult>) {
counter.get() shouldBe 6
}
}