Skip to content

Commit 918185f

Browse files
authored
Merge pull request #23 from rcardin/raise-dsl-assertions
Added assertions for the `Raise<E>.() -> A` type.
2 parents 179f7ee + 3c727e8 commit 918185f

26 files changed

+391
-3
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88

99
This project provides a set of [AssertJ](https://assertj.github.io/doc/) assertions for the [Arrow](https://arrow-kt.io/) library. In detail, the project provides assertions for the following Arrow types:
1010

11-
- [x] `Either`
12-
- [x] `Option`
11+
- [x] `Either<E, A>`
12+
- [x] `Option<A>`
13+
- [x] `Raise<E>.() -> A`
1314

1415
Maybe you're asking yourself: "Why do we need AssertJ assertions for Arrow types?". The answer is simple: We often use Kotlin and Arrow Kt inside a Java project using Spring Boot. In this case, we already have AssertJ in the classpath as an assertion library. So, why not use it to assert Arrow types?
1516

pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>in.rcard</groupId>
88
<artifactId>assertj-arrow-core</artifactId>
9-
<version>0.1.1-SNAPSHOT</version>
9+
<version>0.2.0-SNAPSHOT</version>
1010

1111
<name>AssertJ fluent assertions for Kotlin Arrow Core library</name>
1212
<description>Rich and fluent assertions for testing Kotlin Arrow Core types</description>
@@ -157,6 +157,11 @@
157157
</goals>
158158
</execution>
159159
</executions>
160+
<configuration>
161+
<args>
162+
<arg>-Xcontext-receivers</arg>
163+
</args>
164+
</configuration>
160165
</plugin>
161166
<plugin>
162167
<groupId>org.apache.maven.plugins</groupId>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ import org.assertj.core.internal.StandardComparisonStrategy
1818
* @param LEFT type of the left value contained in the [Either].
1919
* @param RIGHT type of the right value contained in the [Either].
2020
* @author Riccardo Cardin
21+
* @author Simon Frost
22+
*
23+
* @since 0.0.1
2124
*/
2225
abstract class AbstractEitherAssert<
2326
SELF : AbstractEitherAssert<SELF, LEFT, RIGHT>, LEFT : Any, RIGHT : Any,
@@ -88,6 +91,8 @@ abstract class AbstractEitherAssert<
8891
*
8992
* @param requirement the consumer that will accept the right-sided value for deep asserting.
9093
* @return this assertion object.
94+
*
95+
* @since 0.1.0
9196
*/
9297
fun hasRightValueSatisfying(requirement: (RIGHT) -> Unit): SELF {
9398
assertIsRight()
@@ -142,6 +147,7 @@ abstract class AbstractEitherAssert<
142147
*
143148
* @param requirement the consumer that will accept the left-sided value for deep asserting.
144149
* @return this assertion object.
150+
* @since 0.1.0
145151
*/
146152
fun hasLeftValueSatisfying(requirement: (LEFT) -> Unit): SELF {
147153
assertIsLeft()

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import org.assertj.core.internal.StandardComparisonStrategy
1515
* @param SELF the "self" type of this assertion class.
1616
* @param VALUE type of the value contained in the [Option].
1717
* @author Riccardo Cardin
18+
* @author Simon Frost
19+
* @since 0.0.1
1820
*/
1921
abstract class AbstractOptionAssert<
2022
SELF : AbstractOptionAssert<SELF, VALUE>, VALUE : Any,
@@ -92,6 +94,7 @@ abstract class AbstractOptionAssert<
9294
*
9395
* @param requirement to further assert on the object contained inside the [Option].
9496
* @return this assertion object.
97+
* @since 0.1.0
9598
*/
9699
fun hasValueSatisfying(requirement: (VALUE) -> Unit): SELF {
97100
assertValueIsPresent()
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package `in`.rcard.assertj.arrowcore
2+
3+
import arrow.core.raise.Raise
4+
import arrow.core.raise.fold
5+
import `in`.rcard.assertj.arrowcore.errors.RaiseShouldFailButSucceeded.Companion.shouldFailButSucceeded
6+
import `in`.rcard.assertj.arrowcore.errors.RaiseShouldFailWith.Companion.shouldFailWith
7+
import `in`.rcard.assertj.arrowcore.errors.RaiseShouldSucceedButFailed.Companion.shouldSucceedButFailed
8+
import `in`.rcard.assertj.arrowcore.errors.RaiseShouldSucceedWith.Companion.shouldSucceedWith
9+
import org.assertj.core.api.AbstractAssert
10+
import org.assertj.core.internal.ComparisonStrategy
11+
import org.assertj.core.internal.StandardComparisonStrategy
12+
13+
/**
14+
* Assertions for functions within a [Raise] context.
15+
*
16+
* @param VALUE type of the value returned by the function.
17+
* @param ERROR type of the logical error raised by the function.
18+
* @author Riccardo Cardin
19+
* @since 0.2.0
20+
*/
21+
abstract class AbstractRaiseAssert<
22+
SELF : AbstractRaiseAssert<SELF, ERROR, VALUE>, ERROR : Any, VALUE : Any,
23+
>(lambda: context(Raise<ERROR>) () -> VALUE) :
24+
AbstractAssert<SELF, context(Raise<ERROR>) () -> VALUE>(lambda, AbstractRaiseAssert::class.java) {
25+
26+
private val comparisonStrategy: ComparisonStrategy = StandardComparisonStrategy.instance()
27+
28+
/**
29+
* Verifies that the function in the [Raise] context succeeds with the given value.
30+
* @param expectedValue the expected value returned by the function.
31+
*/
32+
fun succeedsWith(expectedValue: VALUE) {
33+
fold(
34+
block = actual,
35+
recover = { actualError: ERROR -> throwAssertionError(shouldSucceedButFailed(expectedValue, actualError)) },
36+
transform = { actualValue ->
37+
if (!comparisonStrategy.areEqual(actualValue, expectedValue)) {
38+
throwAssertionError(shouldSucceedWith(expectedValue, actualValue))
39+
}
40+
},
41+
)
42+
}
43+
44+
/**
45+
* Verifies that the function in the [Raise] context fails with the given error.
46+
* @param expectedError the expected error raised by the function.
47+
*/
48+
fun raises(expectedError: ERROR) {
49+
fold(
50+
block = actual,
51+
recover = { actualError ->
52+
if (!comparisonStrategy.areEqual(actualError, expectedError)) {
53+
throwAssertionError(shouldFailWith(expectedError, actualError))
54+
}
55+
},
56+
transform = { actualValue ->
57+
throwAssertionError(
58+
shouldFailButSucceeded(expectedError, actualValue)
59+
)
60+
},
61+
)
62+
}
63+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import arrow.core.Either
88
* @param LEFT type of the value on the left contained in the [Either].
99
* @param RIGHT type of the value on the right contained in the [Either].
1010
* @author Riccardo Cardin
11+
* @since 0.0.1
1112
*/
1213
class EitherAssert<LEFT : Any, RIGHT : Any>(either: Either<LEFT, RIGHT>?) :
1314
AbstractEitherAssert<EitherAssert<LEFT, RIGHT>, LEFT, RIGHT>(either) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import arrow.core.Option
77
*
88
* @param VALUE type of the value contained in the [Option].
99
* @author Riccardo Cardin
10+
* @since 0.0.1
1011
*/
1112
class OptionAssert<VALUE : Any>(option: Option<VALUE>?) :
1213
AbstractOptionAssert<OptionAssert<VALUE>, VALUE>(option) {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
@file:OptIn(ExperimentalTypeInference::class)
2+
3+
package `in`.rcard.assertj.arrowcore
4+
5+
import arrow.core.raise.Raise
6+
import arrow.core.raise.fold
7+
import `in`.rcard.assertj.arrowcore.errors.RaiseShouldThrowAnException.Companion.shouldThrowAnException
8+
import org.assertj.core.api.AbstractThrowableAssert
9+
import org.assertj.core.api.Assertions
10+
import org.assertj.core.internal.Failures
11+
import kotlin.experimental.ExperimentalTypeInference
12+
13+
/**
14+
* Assertions for functions within a [Raise] context.
15+
*
16+
* @param VALUE type of the value returned by the function.
17+
* @param ERROR type of the logical error raised by the function.
18+
* @author Riccardo Cardin
19+
*
20+
* @since 0.2.0
21+
*/
22+
class RaiseAssert<ERROR : Any, VALUE : Any> private constructor(lambda: Raise<ERROR>.() -> VALUE) :
23+
AbstractRaiseAssert<RaiseAssert<ERROR, VALUE>, ERROR, VALUE>(lambda) {
24+
companion object {
25+
fun <ERROR : Any, VALUE : Any> assertThat(
26+
@BuilderInference lambda: Raise<ERROR>.() -> VALUE
27+
): RaiseAssert<ERROR, VALUE> =
28+
RaiseAssert(lambda)
29+
30+
/**
31+
* Verifies that the function in the [Raise] context throws an exception.
32+
* @param shouldRaiseThrowable the function to be executed in the [Raise] context.
33+
* @return the [AbstractThrowableAssert] to be used to verify the exception.
34+
*/
35+
fun <ERROR : Any, VALUE : Any> assertThatThrownBy(
36+
@BuilderInference shouldRaiseThrowable: Raise<ERROR>.() -> VALUE
37+
): AbstractThrowableAssert<*, out Throwable> {
38+
val throwable: Throwable? = fold(block = shouldRaiseThrowable,
39+
recover = { null },
40+
transform = { null },
41+
catch = { exception -> exception })
42+
43+
@Suppress("KotlinConstantConditions")
44+
return throwable?.let { return Assertions.assertThat(throwable) } ?: throw Failures.instance()
45+
.failure(Assertions.assertThat(throwable).writableAssertionInfo, shouldThrowAnException())
46+
}
47+
}
48+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.assertj.core.error.BasicErrorMessageFactory
77
* Build error message when an [Either] should be left.
88
*
99
* @author Riccardo Cardin
10+
* @since 0.0.1
1011
*/
1112
internal class EitherShouldBeLeft(actual: Either<*, *>) :
1213
BasicErrorMessageFactory("%nExpecting an Either to be left but was <$actual>.") {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.assertj.core.error.BasicErrorMessageFactory
77
* Build error message when an [Either] should be right.
88
*
99
* @author Riccardo Cardin
10+
* @since 0.0.1
1011
*/
1112
internal class EitherShouldBeRight(actual: Either<*, *>) :
1213
BasicErrorMessageFactory("%nExpecting an Either to be right but was <$actual>.") {

0 commit comments

Comments
 (0)