Skip to content

Commit

Permalink
chore: support coroutines
Browse files Browse the repository at this point in the history
  • Loading branch information
ldorival committed Jan 16, 2023
1 parent c974336 commit fddc39a
Show file tree
Hide file tree
Showing 13 changed files with 938 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,33 @@ class MyTest {
}
````

## Coroutine support

> The library supports also **coroutine** functions.
````kotlin
import io.github.ludorival.kotlintdd.coroutine.CoSimpleGivenWhenThen.given
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest

@OptIn(ExperimentalCoroutinesApi::class)
class MyTest {

@Test
fun `I can write my Coroutine test with my custom DSL`() = runTest {
given {
1
} and {
2
} `when` {
`I perform their sum`
} then {
`I expect the result is`(3)
}
}

}
````
# Getting started

Kotlin-TDD is available via Maven Central. Just add the dependency to your Maven POM or Gradle build config.
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,6 @@ tasks.koverVerify {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.1'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.github.ludorival.kotlintdd.coroutine

import io.github.ludorival.kotlintdd.WithContext

abstract class CoAssumeActAssert<R1 : WithContext, R2 : WithContext, R3 : WithContext>(
assumption: R1, action: R2, assertion: R3
) : CoBasePattern<R1, R2, R3>(assumption, action, assertion) {
suspend fun <R> assume(block: suspend R1.() -> R) = assume("ASSUME", block)
suspend fun <R> act(block: suspend R2.() -> R) = act("ACT", block)
}

object CoSimpleAssumeActAssert :
CoAssumeActAssert<WithContext, WithContext, WithContext>(WithContext(), WithContext(), WithContext())

suspend infix fun <T, V,
R1 : WithContext,
R2 : WithContext,
R3 : WithContext> CoBasePattern.AssumptionContext<T, R1, R2, R3>.act(
block: suspend R2.(T) -> V
) = chainAct("ACT", block)

suspend infix fun <T, V,
R1 : WithContext,
R2 : WithContext,
R3 : WithContext> CoBasePattern.ActContext<T, R1, R2, R3>.assert(
block: suspend R3.(T) -> V
) = chainAssert("ASSERT", block)
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package io.github.ludorival.kotlintdd.coroutine

import io.github.ludorival.kotlintdd.Context
import io.github.ludorival.kotlintdd.WithContext


open class CoBasePattern<R1 : WithContext, R2 : WithContext, R3 : WithContext>(
private val assumption: R1,
private val action: R2,
private val assertion: R3
) {

class AssumptionContext<T, R1 : WithContext, R2 : WithContext, R3 : WithContext> internal constructor(
private val pattern: CoBasePattern<R1, R2, R3>,
key: String,
result: T

) :
Context<T>(result, key) {

internal suspend fun <V> chainAct(key: String, block: suspend R2.(T) -> V) =
chain(ActContext(pattern, key, pattern.actionReceiver(this).block(result)))

suspend infix fun <V> and(block: suspend R1.(T) -> V): AssumptionContext<V, R1, R2, R3> =
chain(AssumptionContext(pattern, "AND", pattern.assumptionReceiver(this).block(result)))

internal suspend fun <V> chainAssert(key: String, block: suspend R3.(T) -> V) =
chain(AssertContext(pattern, key, pattern.assertionReceiver(this).block(result)))

}

class ActContext<T, R1 : WithContext, R2 : WithContext, R3 : WithContext> internal constructor(
private val pattern: CoBasePattern<R1, R2, R3>,
key: String,
result: T

) :
Context<T>(result, key) {
suspend infix fun <V> and(block: suspend R2.(T) -> V): ActContext<V, R1, R2, R3> =
chain(ActContext(pattern, "AND", pattern.actionReceiver(this).block(result)))

internal suspend fun <V> chainAssert(key: String, block: suspend R3.(T) -> V) =
chain(AssertContext(pattern, key, pattern.assertionReceiver(this).block(result)))

}

class AssertContext<T, R1 : WithContext, R2 : WithContext, R3 : WithContext> internal constructor(
private val pattern: CoBasePattern<R1, R2, R3>,
key: String,
result: T

) :
Context<T>(result, key) {
suspend infix fun <V> and(block: suspend R3.(T) -> V): AssertContext<V, R1, R2, R3> =
chain(AssertContext(pattern, "AND", pattern.assertionReceiver(this).block(result)))

}

private fun <T> assumptionReceiver(context: Context<T>): R1 = assumption.apply { with(context) }
private fun <T> actionReceiver(context: Context<T>): R2 = action.apply { with(context) }
private fun <T> assertionReceiver(context: Context<T>): R3 = assertion.apply { with(context) }


suspend fun <V> assume(key: String, block: suspend R1.() -> V): AssumptionContext<V, R1, R2, R3> =
AssumptionContext(this, key, assumptionReceiver(Context.EMPTY_CONTEXT).block())

suspend fun <V> act(key: String, block: suspend R2.() -> V): ActContext<V, R1, R2, R3> =
ActContext(this, key, actionReceiver(Context.EMPTY_CONTEXT).block())

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.github.ludorival.kotlintdd.coroutine

import io.github.ludorival.kotlintdd.WithContext

@Suppress("FunctionNaming")
abstract class CoGivenWhenThen<R1 : WithContext, R2 : WithContext, R3 : WithContext>(
assumption: R1,
action: R2,
assertion: R3
) : CoBasePattern<R1, R2, R3>(assumption, action, assertion) {
suspend fun <R> given(block: suspend R1.() -> R) = assume("GIVEN", block)
suspend fun <R> `when`(block: suspend R2.() -> R) = act("WHEN", block)
}

object CoSimpleGivenWhenThen :
CoGivenWhenThen<WithContext, WithContext, WithContext>(WithContext(), WithContext(), WithContext())

@Suppress("FunctionNaming")
suspend infix fun <T, V, R1 : WithContext, R2 :
WithContext, R3 : WithContext> CoBasePattern.AssumptionContext<T, R1, R2, R3>.`when`(
block: suspend R2.(T) -> V
) =
chainAct("WHEN", block)

suspend infix fun <T, V, R1 : WithContext, R2 : WithContext,
R3 : WithContext> CoBasePattern.ActContext<T, R1, R2, R3>.then(block: suspend R3.(T) -> V) =
chainAssert("THEN", block)

suspend infix fun <T, V, R1 : WithContext, R2 : WithContext,
R3 : WithContext> CoBasePattern.AssumptionContext<T, R1, R2, R3>.then(
block: suspend R3.(T) -> V
) =
chainAssert("THEN", block)
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package io.github.ludorival.kotlintdd.coroutine.aaa

import io.github.ludorival.kotlintdd.coroutine.act
import io.github.ludorival.kotlintdd.coroutine.assert
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import io.github.ludorival.kotlintdd.coroutine.CoSimpleAssumeActAssert.assume as assumeNoAction

@OptIn(ExperimentalCoroutinesApi::class)
internal class CoAssumeActAssertTest {

@Test
fun `I can use simple assume act assert pattern`() = runTest {
assume {
1
} act {
sum(it, 2)
} assert {
3 `should be equal to` it
} and {

"""
ASSUME -> 1
ACT -> 3
ASSERT -> *Something*""".trimIndent() `should be equal to` toString()

}
}

@Test
fun `I can use assume act assert pattern with and`() = runTest {
assume {
1
} and {
2
} act {
sum(first(), last())
} assert {
it `should be equal to` 3
} and {
"""
ASSUME -> 1
AND -> 2
ACT -> 3
ASSERT -> *Something*""".trimIndent() `should be equal to` toString()
}
}


@Test
fun `I can use directly the act instead of the assume`() = runTest {
act {
sum(1, 2)
} assert {
assertEquals(it, 3)
} and {
assertEquals(
"""
ACT -> 3
ASSERT -> *Something*""".trimIndent(), toString()
)
}
}


@Test
fun `I can support nested steps from the ASSUME`() = runTest {
assume {
someUseCaseWithoutExtension()
} and {
4
} act {
sum(results())
} assert {
assertEquals(it, 1 + 2 + 3 + 4)
} and {
assertEquals(
"""
ASSUME -> 1
AND -> 2
AND -> 3
AND -> 4
ACT -> 10
ASSERT -> *Something*""".trimIndent(), toString()
)
}
}

@Test
fun `I can support nested steps after the ASSUME`() = runTest {
assume {
4
} and {
someUseCaseWithoutExtension()
} act {
sum(results())
} assert {
assertEquals(it, 4 + 1 + 2 + 3)
} and {
assertEquals(
"""
ASSUME -> 4
ASSUME -> 1
AND -> 2
AND -> 3
ACT -> 10
ASSERT -> *Something*""".trimIndent(), toString()
)
}
}

@Test
fun `I can support multiple nested steps`() = runTest {
assume {
4
} and {
someNestedUseCaseWithoutExtension()
} act {
sum(results())
} assert {
assertEquals(4 + 5 + 1 + 2 + 3 + 6, it)
} and {
assertEquals(
"""
ASSUME -> 4
ASSUME -> 5
ASSUME -> 1
AND -> 2
AND -> 3
AND -> 6
ACT -> 21
ASSERT -> *Something*""".trimIndent(), toString()
)
}
}

@Test
fun `I can support nested steps by using extension after the ASSUME`() = runTest {
assume {
4
} and {
someUseCaseWithoutExtension()
} act {
sum(results())
} assert {
assertEquals(it, 4 + 1 + 2 + 3)
} and {
assertEquals(
"""
ASSUME -> 4
ASSUME -> 1
AND -> 2
AND -> 3
ACT -> 10
ASSERT -> *Something*""".trimIndent(), toString()
)
}
}

@Test
fun `I can support multiple nested steps by using extension`() = runTest {
assume {
4
} and {
someNestedUseCaseWithoutExtension()
} act {
sum(results())
} assert {
assertEquals(4 + 5 + 1 + 2 + 3 + 6, it)
} and {
assertEquals(
"""
ASSUME -> 4
ASSUME -> 5
ASSUME -> 1
AND -> 2
AND -> 3
AND -> 6
ACT -> 21
ASSERT -> *Something*""".trimIndent(), toString()
)
}
}

@Test
fun `I can use a pattern without defining an action`() = runTest {
assumeNoAction {
1
} act {
it * 3
} assert {
assertEquals(3, it)
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.github.ludorival.kotlintdd.coroutine.aaa

import io.github.ludorival.kotlintdd.coroutine.CoAssumeActAssert
import io.github.ludorival.kotlintdd.dsl.Action
import io.github.ludorival.kotlintdd.dsl.Assertion
import io.github.ludorival.kotlintdd.dsl.Assumption

object UnitTest : CoAssumeActAssert<Assumption, Action, Assertion>(
assumption = Assumption(),
action = Action(),
assertion = Assertion()
)

suspend fun <R> assume(block: suspend Assumption.() -> R) = UnitTest.assume(block)
suspend fun <R> act(block: suspend Action.() -> R) = UnitTest.act(block)
Loading

0 comments on commit fddc39a

Please sign in to comment.