https://kotlinlang.org logo
#mockk
Title
# mockk
p

Pihentagy

03/22/2024, 2:41 PM
Hi, I am using mockk with spring boot. How can I verify, that a SpykBean myService calls foo() and verify the return value of foo() and verify it was "hello"?
s

Szymon Jeziorski

03/22/2024, 3:33 PM
You can act on return value of the call by using
callsOriginal()
method within
answers
block Example:
Copy code
var returnedResult: String? = null
every {
    myService.foo()
} answers {
    callOriginal().also { returnedResult = it }

(invoking tested logic)

assertThat(returnedResult).isEqualTo("hello")
👍 1
p

Pihentagy

03/22/2024, 3:42 PM
(Found CapturingSlot ITMT). What extra does it offer?
And about assertThat: if returnedResult and "hello" is replaced by a List, it does not complain if the types are incompatible. How is that? (I know its assertj, not mockk)
s

Szymon Jeziorski

03/22/2024, 3:54 PM
Capturing slots allow you to "capture" passed arguments to mocked methods/functions, but not returned results, so they wouldn't fit your use case. They would however come in handy if you had
foo(bar: String): String
and wanted to act on passed
bar
argument (or arguments in case you used MutableList for capturing slot). Unfortunately
isEqualTo
method from AssertJ just accepts
Object
as argument so it's the API itself that lacks type-safety. I used it just for the example, but you may use any other assertion framework which has more type-safe APIs (I very much dislike AssertJ for that very thing, I'm still using it in work projects as it has the most extensive API I've found which can simplify many edge cases (especially for recursive comparisions, using custom comparators etc))
p

Pihentagy

03/22/2024, 4:17 PM
Cannot just add a thin wrapper to assertj and live happily with that? Btw the decompiled source seems to be typesafe but I tested with different types of lists and it happily accepted
s

Szymon Jeziorski

03/22/2024, 4:45 PM
Well, that's not always that simple. When you have
assertThat("string")
then resulting type is
AbstractStringAssert<*>
which has type information, but often when you use little more advanced constructs from AssertJ then type information is lost somewhere in between:
Copy code
// extension method to make API a little more kotlin-friendly
inline fun <reified T : Any> RecursiveComparisonAssert<*>.withComparatorForType(
    noinline comparator: (T, T) -> Int
): RecursiveComparisonAssert<*> = withComparatorForType(comparator, T::class.java)

// just an example to showcase API, of course could be asserted with simplier API
class ComplexObject(val name: String, val height: BigDecimal) {
    val id = UUID.randomUUID().toString()
}

@Test
fun `assertJ test`() {
    val complexObject = ComplexObject("sampleName", BigDecimal("1.20"))
    val expectedObject = ComplexObject("sampleName", BigDecimal("1.200"))

    assertThat(complexObject) // AbstractObjectAssert<ComplexObject>
        .usingRecursiveComparison() // RecursiveComparisonAssert<*> - type information already lost
        .withComparatorForType(BigDecimal::compareTo) // ensuring scales are not affecting assertion result
        .ignoringFields(ComplexObject::id.name) // ignoring field not being cared about
        .isEqualTo(expectedObject)
}
I could write extensions for cases where type information is preserved, and that might be a good idea ineed, but that wouldn't work for more advanced cases. And for your case with decompiled source code - AbstractStringAssert has
isEqualTo(String expected)
method, but in the same time it inherits from
AbstractAssert
which has base method
isEqualTo(Object expected
). Therefore invoking
isEqualTo
with not-compatilble argument type-wise would just fall back to it, and that's what you experienced
3 Views