assertj: Kotlin SAM overload resolution ambiguity between Consumer and ThrowingConsumer
Summary
When using AssertJ Core 3.21.0 from Kotlin, an overload resolution ambiguity occurrs for methods having overloads which accept both Consumer and ThrowingConsumer.
Methods affected (list noncomprehensive)
org.assertj.core.api.AbstractAssert#satisfies(java.util.function.Consumer<? super ACTUAL>)
org.assertj.core.api.ObjectEnumerableAssert#allSatisfy(org.assertj.core.api.ThrowingConsumer<? super ELEMENT>)
Example
Worked before (typical syntax for Kotlin calling a method which takes a functional interface AKA “SAM”)
assertThat(bounds).satisfies { // worked pre-3.21.0
assertThat(it.startInclusive) // ... any detail assert statement
}
Wordy workaround:
assertThat(bounds).satisfies(Consumer {
assertThat(it.startInclusive) // ... any detail assert statement
})
This overload ambiguity is (also) an issue of kotlin language design, cf. KT-17765
About this issue
- Original URL
- State: open
- Created 3 years ago
- Reactions: 23
- Comments: 30 (13 by maintainers)
Commits related to this issue
- Workaround issues with ambiguous AssertJ overloads within Kotlin See https://github.com/assertj/assertj-core/issues/2357 - ugh. — committed to ThoughtWorks-SEA/recce by chadlwilson 2 years ago
Alright, let’s reopen this one.
I think it’s too late to introduce further breaking changes in version 3 for this topic, but version 4 may be the right time to rethink how to better support Kotlin.
I’ve created the following workaround in my tests. For the record, I still believe a more sensible approach would have been to name the new API
satisfiesThrowable
or similar.Kotlin 1.7 works with AssertJ 3.21.0. It won’t however work with AssertJ 3.22.0, as a new problem appears:
Note this is the same
satisfies
method but a different error message than the one originally reported here. Also the reason is different: Kotlin compiler is unable to properly handle vararg use of Consumer and ThrowingConsumer. Reported as a separate issue: KT-52942. If you found my comment because you experience this new problem, upvote the issue in Jetbrains’ issue tracker.I had the same problem after I updated spring boot. For me, the problem was solved by downgrade assertj-core to version
3.20.2
(testImplementation("org.assertj:assertj-core:3.20.2")
). I guess that’s not a long-term solution, but a workaround for now.Though KT-17765 seems fixed in Kotlin 1.6.20-M1 it requires using experimental language version 1.7, e.g.:
My two cents:
I’ve tested using experimental language version 1.7 as suggested and it works… kinda
As can be seen in the attached image, braces around the
Consumer
lambda are still required…KT-17765 is now fixed and is targeted for 1.6.20 🎉
See also https://youtrack.jetbrains.com/issue/KT-53113/Kotlin-AssertJ-tests-compile-in-1621-but-not-in-17
It’s an disturbing blame game between AssertJ and Kotlin with fingerpointing on both sides but still no solution, even with Kotlin 1.7. Besides a manual downgrade to 3.20.2 (3.21 did not work):
AssertJ (as Bundled with Spring Boot 2.6+) does simply not work with Kotlin currently.
Compilation error occurs during Gradle build as well… I am using Kotlin Plugin
213-1.6.20-release-275-IJ6777.52
I do not think that this can be fixed in Kotlin. It is not a bug in the language. To identify SAM interfaces, we can take arguments to the called function and the result type into consideration. If after that, more than one interface matches, we are out of luck. Now exceptions are simply not taken into consideration here – and this is by design.
I somehow seem to remember a similar issue with
.extracting()
– was there someting like aThrowingFunction
once upon a time?BTW, the work-around is super-ugly with our standard formatter, which will put the
Consumer {
on a line by itself and requires another level of indentation. I wrote asatifisfiesSoftly()
extension function just to avoid this (and add softness on top). I would very much wish for AssertJ to stay compatible with Kotlin. I know there are other assertion libraries, but they are just not up to par.