Is there any requirement on implementing `Raise` ?...
# arrow
c
Is there any requirement on implementing
Raise
? It's really tempting to create a
Copy code
object TestRaise : Raise<Any> {
    override suspend fun <B> raise(r: Any): Nothing {
        throw AssertionError("Unexpected result from $r")
    }
}
to be able to write tests like
Copy code
runTest {
    val result = expensiveOperation().bind()
    result shouldBe 42
}
instead of
Copy code
runTest {
    val result = expensiveOperation()
    result shouldBe 42.right()
}
g
can you make use of the arrow assertions?
Copy code
result.shouldBeRight(42)
c
It's more convenient to failfast than to use an either result. It also works with non-either types that use the Raise DSL (which the arrow assertions cannot manage to my knowledge)
y
Why not just have
runTest
be:
Copy code
inline fun runTest(block: Raise<Any>().() -> Unit) {
  either {
    block().mapLeft { throw AssertionError("Unexpected result from $it") }
  }
}
In general, there's not really a reason to implement
Raise
, and the only reason it's an interface is because of
NullableRaise
,
OptionRaise
, etc, which will disappear when context receivers are stable
s
I would advise against it. The suggestion that @Youssef Shoaib [MOD] made is a much better solution, and there is many different styles DSLs you could come-up with.
p
As another example, we use a similar pattern to Youssef’s. Our code looks like the screenshot below where a “respond” function takes care of handling the either.
s
@P A really neat use of context receivers, and Arrow with Spring 😍
c
@Youssef Shoaib [MOD] but then the assertion's stack trace doesn't point to the location of the error, does it? @P A I don't understand your point, the question was about tests?
y
Use the new tracing API that got merged into arrow recently (not sure if it's out yet in a version or not, so you might need to wait). It allows you to access the stack trace of the line that called
raise
I believe P A's point was that the
respond
function does something similar internally where it takes care of throwing an exception (likely one with an HTTP status code or something) in case of a raised value, so it's a similar pattern as what you're doing for tests
c
@Youssef Shoaib [MOD] Oh, I forgot about it! Then, I'll keep my
Raise
receiver in tests, keep the implementation that throws an AssertionError for now, and replace it with the tracing API whenever that reaches my projects.
s
The traces API has been released in 1.2.0-RC. I slapped
OptIn
on it though 😅 So please test and use it, so we can remove it before 2.0 or refine the API first.
c
I don't see anything in the docs about it, how should I use it?
y
https://github.com/arrow-kt/arrow/blob/main/arrow-libs/core/arrow-core/src/jvmTest/kotlin/arrow/core/raise/TraceJvmSpec.kt#L7:
Copy code
either<RuntimeException, Nothing> {
      traced({ raise(RuntimeException("")) }) { traced, raised ->
        // Remove first 2 lines:
        // arrow.core.raise.RaiseCancellationException
        //	at arrow.core.raise.DefaultRaise.raise(Fold.kt:187)
        val trace = traced.stackTraceToString().lines().drop(2)

        // Remove first line:
        // java.lang.RuntimeException:
        val exceptionTrace = raised.stackTraceToString().lines().drop(1)

        trace shouldBe exceptionTrace
      }
    }
Shows how to get the stack trace out of it.