Skip to content

Commit 4930ed8

Browse files
authored
fix(49): Moved Raise evaluation on assertThat function
Assertions on `Raise<E>.() -> A` dont work on suspending functions
2 parents 0c77d87 + 62ef1a5 commit 4930ed8

File tree

11 files changed

+180
-92
lines changed

11 files changed

+180
-92
lines changed

pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<junit-jupiter.version>5.10.3</junit-jupiter.version>
3333
<dokka-maven-plugin.version>1.9.0</dokka-maven-plugin.version>
3434
<maven-surefire-plugin.version>3.3.1</maven-surefire-plugin.version>
35+
<kotlinx-coroutines-test.version>1.9.0-RC.2</kotlinx-coroutines-test.version>
3536
</properties>
3637

3738
<url>https://github.com/rcardin/assertj-arrow-core</url>
@@ -84,6 +85,13 @@
8485
<version>${kotlin-stdlib.version}</version>
8586
<scope>test</scope>
8687
</dependency>
88+
<dependency>
89+
<groupId>org.jetbrains.kotlinx</groupId>
90+
<artifactId>kotlinx-coroutines-test</artifactId>
91+
<version>${kotlinx-coroutines-test.version}</version>
92+
<type>pom</type>
93+
<scope>test</scope>
94+
</dependency>
8795
</dependencies>
8896

8997
<build>
Lines changed: 77 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package `in`.rcard.assertj.arrowcore
22

33
import arrow.core.raise.Raise
4-
import arrow.core.raise.fold
54
import `in`.rcard.assertj.arrowcore.errors.RaiseShouldFailButSucceeds.Companion.shouldFailButSucceedsWith
65
import `in`.rcard.assertj.arrowcore.errors.RaiseShouldFailButSucceeds.Companion.shouldFailWithButSucceedsWith
76
import `in`.rcard.assertj.arrowcore.errors.RaiseShouldFailWith.Companion.shouldFailWith
@@ -25,84 +24,119 @@ abstract class AbstractRaiseAssert<
2524
ERROR : Any,
2625
VALUE : Any,
2726
> internal constructor(
28-
lambda: Raise<ERROR>.() -> VALUE,
27+
raiseResult: RaiseResult<ERROR, VALUE>,
2928
) : AbstractAssert<
3029
SELF,
31-
Raise<ERROR>.() -> VALUE,
32-
>(lambda, AbstractRaiseAssert::class.java) {
30+
RaiseResult<ERROR, VALUE>,
31+
>(raiseResult, AbstractRaiseAssert::class.java) {
3332
private val comparisonStrategy: ComparisonStrategy = StandardComparisonStrategy.instance()
3433

3534
/**
3635
* Verifies that the function in the [Raise] context succeeds with the given value.
3736
* @param expectedValue the expected value returned by the function.
3837
*/
39-
fun succeedsWith(expectedValue: VALUE) {
40-
fold(
41-
block = actual,
42-
recover = { actualError: ERROR ->
38+
fun succeedsWith(expectedValue: VALUE) =
39+
when (actual) {
40+
is RaiseResult.Failure<ERROR> -> {
4341
throwAssertionError(
44-
shouldSucceedWithButFailed(
45-
expectedValue,
46-
actualError,
47-
),
42+
shouldSucceedWithButFailed(expectedValue, (actual as RaiseResult.Failure<ERROR>).error),
4843
)
49-
},
50-
transform = { actualValue ->
44+
}
45+
46+
is RaiseResult.FailureWithException -> {
47+
throw (actual as RaiseResult.FailureWithException).exception
48+
}
49+
50+
is RaiseResult.Success<VALUE> -> {
51+
val actualValue = (actual as RaiseResult.Success<VALUE>).value
5152
if (!comparisonStrategy.areEqual(actualValue, expectedValue)) {
5253
throwAssertionError(shouldSucceedWith(expectedValue, actualValue))
54+
} else {
55+
// Nothing to do
5356
}
54-
},
55-
)
56-
}
57+
}
58+
}
5759

5860
/**
5961
* Verifies that the function in the [Raise] context succeeded. No check on the value returned by the function is
6062
* performed.
6163
*
6264
* @see succeedsWith
6365
*/
64-
fun succeeds() {
65-
fold(
66-
block = actual,
67-
recover = { actualError: ERROR -> throwAssertionError(shouldSucceedButFailed(actualError)) },
68-
transform = { _ -> },
69-
)
70-
}
66+
fun succeeds() =
67+
when (actual) {
68+
is RaiseResult.Failure<ERROR> ->
69+
throwAssertionError(
70+
shouldSucceedButFailed((actual as RaiseResult.Failure<ERROR>).error),
71+
)
72+
73+
is RaiseResult.FailureWithException -> {
74+
throw (actual as RaiseResult.FailureWithException).exception
75+
}
76+
77+
is RaiseResult.Success<VALUE> -> {
78+
// Nothing to do
79+
}
80+
}
7181

7282
/**
7383
* Verifies that the function in the [Raise] context fails with the given error.
7484
* @param expectedError the expected error raised by the function.
7585
*/
76-
fun raises(expectedError: ERROR) {
77-
fold(
78-
block = actual,
79-
recover = { actualError ->
86+
fun raises(expectedError: ERROR) =
87+
when (actual) {
88+
is RaiseResult.Failure<ERROR> -> {
89+
val actualError = (actual as RaiseResult.Failure<ERROR>).error
8090
if (!comparisonStrategy.areEqual(actualError, expectedError)) {
8191
throwAssertionError(shouldFailWith(expectedError, actualError))
92+
} else {
93+
// Nothing to do
8294
}
83-
},
84-
transform = { actualValue ->
95+
}
96+
97+
is RaiseResult.FailureWithException -> {
98+
throw (actual as RaiseResult.FailureWithException).exception
99+
}
100+
101+
is RaiseResult.Success<VALUE> -> {
85102
throwAssertionError(
86-
shouldFailWithButSucceedsWith(expectedError, actualValue),
103+
shouldFailWithButSucceedsWith(expectedError, (actual as RaiseResult.Success<VALUE>).value),
87104
)
88-
},
89-
)
90-
}
105+
}
106+
}
91107

92108
/**
93109
* Verifies that the function in the [Raise] context fails, no matter the type of the logical error.
94110
*
95111
* @see raises
96112
*/
97-
fun fails() {
98-
fold(
99-
block = actual,
100-
recover = { _ -> },
101-
transform = { actualValue ->
113+
fun fails() =
114+
when (actual) {
115+
is RaiseResult.Failure<ERROR> -> {
116+
// Nothing to do
117+
}
118+
119+
is RaiseResult.FailureWithException -> {
120+
throw (actual as RaiseResult.FailureWithException).exception
121+
}
122+
123+
is RaiseResult.Success ->
102124
throwAssertionError(
103-
shouldFailButSucceedsWith(actualValue),
125+
shouldFailButSucceedsWith((actual as RaiseResult.Success<VALUE>).value),
104126
)
105-
},
106-
)
107-
}
127+
}
128+
}
129+
130+
sealed interface RaiseResult<out ERROR : Any, out VALUE : Any> {
131+
data class Success<out VALUE : Any>(
132+
val value: VALUE,
133+
) : RaiseResult<Nothing, VALUE>
134+
135+
data class Failure<out ERROR : Any>(
136+
val error: ERROR,
137+
) : RaiseResult<ERROR, Nothing>
138+
139+
data class FailureWithException(
140+
val exception: Throwable,
141+
) : RaiseResult<Nothing, Nothing>
108142
}

src/main/kotlin/in/rcard/assertj/arrowcore/RaiseAssert.kt

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,42 @@ import kotlin.experimental.ExperimentalTypeInference
1919
*
2020
* @since 0.2.0
2121
*/
22-
class RaiseAssert<ERROR : Any, VALUE : Any> private constructor(lambda: Raise<ERROR>.() -> VALUE) :
23-
AbstractRaiseAssert<RaiseAssert<ERROR, VALUE>, ERROR, VALUE>(lambda) {
22+
class RaiseAssert<ERROR : Any, VALUE : Any>(
23+
raiseResult: RaiseResult<ERROR, VALUE>,
24+
) : AbstractRaiseAssert<RaiseAssert<ERROR, VALUE>, ERROR, VALUE>(raiseResult) {
2425
companion object {
25-
fun <ERROR : Any, VALUE : Any> assertThat(
26-
@BuilderInference lambda: Raise<ERROR>.() -> VALUE
27-
): RaiseAssert<ERROR, VALUE> =
28-
RaiseAssert(lambda)
26+
inline fun <ERROR : Any, VALUE : Any> assertThat(
27+
@BuilderInference lambda: Raise<ERROR>.() -> VALUE,
28+
): RaiseAssert<ERROR, VALUE> {
29+
val raiseResult =
30+
fold(
31+
block = lambda,
32+
catch = { throwable -> RaiseResult.FailureWithException(throwable) },
33+
recover = { error -> RaiseResult.Failure(error) },
34+
transform = { value -> RaiseResult.Success(value) },
35+
)
36+
return RaiseAssert(raiseResult)
37+
}
2938

3039
/**
3140
* Verifies that the function in the [Raise] context throws an exception.
3241
* @param shouldRaiseThrowable the function to be executed in the [Raise] context.
3342
* @return the [AbstractThrowableAssert] to be used to verify the exception.
3443
*/
35-
fun <ERROR : Any, VALUE : Any> assertThatThrownBy(
36-
@BuilderInference shouldRaiseThrowable: Raise<ERROR>.() -> VALUE
44+
inline fun <ERROR : Any, VALUE : Any> assertThatThrownBy(
45+
@BuilderInference shouldRaiseThrowable: Raise<ERROR>.() -> VALUE,
3746
): AbstractThrowableAssert<*, out Throwable> {
38-
val throwable: Throwable? = fold(block = shouldRaiseThrowable,
39-
recover = { null },
40-
transform = { null },
41-
catch = { exception -> exception })
47+
val throwable: Throwable? =
48+
fold(
49+
block = shouldRaiseThrowable,
50+
recover = { null },
51+
transform = { null },
52+
catch = { exception -> exception },
53+
)
4254

4355
@Suppress("KotlinConstantConditions")
44-
return throwable?.let { return Assertions.assertThat(throwable) } ?: throw Failures.instance()
56+
return throwable?.let { return Assertions.assertThat(throwable) } ?: throw Failures
57+
.instance()
4558
.failure(Assertions.assertThat(throwable).writableAssertionInfo, shouldThrowAnException())
4659
}
4760
}

src/main/kotlin/in/rcard/assertj/arrowcore/errors/RaiseShouldThrowAnException.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,8 @@ private const val SHOULD_THROW_AN_EXCEPTION_MESSAGE = "%nExpecting code to throw
1010
* @author Riccardo Cardin
1111
* @since 0.2.0
1212
*/
13-
internal class RaiseShouldThrowAnException private constructor() :
14-
BasicErrorMessageFactory(SHOULD_THROW_AN_EXCEPTION_MESSAGE) {
15-
13+
class RaiseShouldThrowAnException private constructor() : BasicErrorMessageFactory(SHOULD_THROW_AN_EXCEPTION_MESSAGE) {
1614
companion object {
17-
internal fun shouldThrowAnException(): RaiseShouldThrowAnException =
18-
RaiseShouldThrowAnException()
15+
fun shouldThrowAnException(): RaiseShouldThrowAnException = RaiseShouldThrowAnException()
1916
}
20-
}
17+
}

src/test/kotlin/in/rcard/assertj/arrowcore/Dummy.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ package `in`.rcard.assertj.arrowcore
33
import arrow.core.raise.Raise
44

55
internal object Dummy {
6+
fun Raise<String>.aFunctionWithContext(input: Int): Int = input
67

7-
context (Raise<String>)
8-
fun aFunctionWithContext(input: Int): Int = input
8+
fun Raise<String>.aFunctionThatRaisesAnError(): Int = raise("LOGICAL ERROR")
99

10-
context (Raise<String>)
11-
fun aFunctionThatRaisesAnError(): Int = raise("LOGICAL ERROR")
10+
fun Raise<String>.aFunctionThatThrowsAnException(): Int = throw RuntimeException("AN EXCEPTION")
1211

13-
context (Raise<String>)
14-
fun aFunctionThatThrowsAnException(): Int = throw RuntimeException("AN EXCEPTION")
12+
suspend fun Raise<String>.aSuspendFunctionWithContext(input: Int): Int = input
13+
14+
suspend fun Raise<String>.aSuspendFunctionThatThrowsAnException(): Int = throw RuntimeException("AN EXCEPTION")
1515
}
Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,50 @@
11
package `in`.rcard.assertj.arrowcore
22

3+
import `in`.rcard.assertj.arrowcore.Dummy.aFunctionThatRaisesAnError
4+
import `in`.rcard.assertj.arrowcore.Dummy.aFunctionThatThrowsAnException
5+
import `in`.rcard.assertj.arrowcore.Dummy.aFunctionWithContext
6+
import `in`.rcard.assertj.arrowcore.Dummy.aSuspendFunctionThatThrowsAnException
37
import `in`.rcard.assertj.arrowcore.RaiseAssert.Companion.assertThatThrownBy
48
import `in`.rcard.assertj.arrowcore.errors.RaiseShouldThrowAnException.Companion.shouldThrowAnException
9+
import kotlinx.coroutines.test.runTest
510
import org.assertj.core.api.Assertions
611
import org.junit.jupiter.api.Test
712

813
internal class RaiseAssert_assertThatThrownBy_Test {
9-
1014
@Test
1115
internal fun `should succeed if the lambda throws an exception`() {
12-
assertThatThrownBy { Dummy.aFunctionThatThrowsAnException() }
16+
assertThatThrownBy { aFunctionThatThrowsAnException() }
1317
.isInstanceOf(RuntimeException::class.java)
1418
.hasMessage("AN EXCEPTION")
1519
}
1620

21+
@Test
22+
internal fun `should succeed if the suspending lambda throws an exception`() =
23+
runTest {
24+
assertThatThrownBy { aSuspendFunctionThatThrowsAnException() }
25+
.isInstanceOf(RuntimeException::class.java)
26+
.hasMessage("AN EXCEPTION")
27+
}
28+
1729
@Test
1830
internal fun `should fail if the lambda succeeds with a value`() {
19-
Assertions.assertThatThrownBy {
20-
assertThatThrownBy { Dummy.aFunctionWithContext(42) }
21-
}.isInstanceOf(AssertionError::class.java)
31+
Assertions
32+
.assertThatThrownBy {
33+
assertThatThrownBy { aFunctionWithContext(42) }
34+
}.isInstanceOf(AssertionError::class.java)
2235
.hasMessage(
2336
shouldThrowAnException().create(),
2437
)
2538
}
2639

2740
@Test
2841
internal fun `should fail if the lambda raises an error`() {
29-
Assertions.assertThatThrownBy {
30-
assertThatThrownBy { Dummy.aFunctionThatRaisesAnError() }
31-
}.isInstanceOf(AssertionError::class.java)
42+
Assertions
43+
.assertThatThrownBy {
44+
assertThatThrownBy { aFunctionThatRaisesAnError() }
45+
}.isInstanceOf(AssertionError::class.java)
3246
.hasMessage(
3347
shouldThrowAnException().create(),
3448
)
3549
}
36-
}
50+
}
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
package `in`.rcard.assertj.arrowcore
22

3+
import `in`.rcard.assertj.arrowcore.Dummy.aFunctionWithContext
4+
import `in`.rcard.assertj.arrowcore.Dummy.aSuspendFunctionWithContext
5+
import kotlinx.coroutines.test.runTest
36
import org.assertj.core.api.BDDAssertions.then
47
import org.junit.jupiter.api.Test
58

69
internal class RaiseAssert_assertThat_Test {
710
@Test
811
internal fun `should create an assertion instance when given lambda is not null`() {
9-
val assertion = RaiseAssert.assertThat { Dummy.aFunctionWithContext(42) }
12+
val assertion = RaiseAssert.assertThat { aFunctionWithContext(42) }
1013
then(assertion).isNotNull.isInstanceOf(RaiseAssert::class.java)
1114
}
15+
16+
@Test
17+
internal fun `should create an assertion instance for suspending lambdas`() =
18+
runTest {
19+
val assertion = RaiseAssert.assertThat { aSuspendFunctionWithContext(42) }
20+
then(assertion).isNotNull.isInstanceOf(RaiseAssert::class.java)
21+
}
1222
}

0 commit comments

Comments
 (0)