Mikhail Konovalov opened SPR-15692 and commented
It seems that due to recursive generics in BodySpec
interface
interface BodySpec<B, S extends BodySpec<B, S>>
and due to expectBody
method returns
<B> BodySpec<B, ?> expectBody(Class<B> bodyType);
WebTestClient
cannot be used in Kotlin.
Kotlin inherits the result of .expectBody(Person::class.java)
as BodySpec<Person, *>
and thus the following methods in chain cannot be constructed due to the following error:
Error:(25, 20) Kotlin: Type inference failed: Not enough information to infer parameter T in fun
isEqualTo(p0: Controller.Person!): T! Please specify it explicitly.
And it applies only Nothing
as a type parameter.
But in this case generated bytecode contains the following line
throw null
Example:
@Test
fun `test get`() {
val expectBody: BodySpec<Person, *> = client.get().uri("/person/42").exchange()
.expectBody(Person::class.java)
expectBody.isEqualTo(Person("42", "Ivan")) // doesn't compile here
expectBody.isEqualTo<BodySpec<Person, *>>(Person("42", "Ivan")) // doesn't compile here
expectBody.isEqualTo<Nothing>(Person("42", "Ivan")) // compile but lead to "throw null" in bytecode
}
If you work with list the situation is a bit better - Kotlin still cannot inherit type param automatically but you can specify it explicitly due to method expectBodyList
in interface ListBodySpec
doesn't return wildcards
<E> ListBodySpec<E> expectBodyList(Class<E> elementType);
Example:
@Test
fun `test list`() {
val expectBodyList: ListBodySpec<Person> = client.get().uri("/person").exchange()
.expectBodyList(Person::class.java)
expectBodyList.consumeWith<ListBodySpec<Person>> { list -> Assert.assertTrue(true) } // need to specify type param explicitly
}
Full example with java and kotlin can be found here. Tests in java works well in these cases.
Affects: 5.0 RC2
Reference URL: https://gist.github.com/mskonovalov/42761bbc548e92c2af16c40cffcfcaf3
Issue Links: - #20606 Unable to use WebTestClient with mock server in Kotlin
Referenced from: commits https://github.com/spring-projects/spring-framework/commit/91c8b62817facb1e7b25c070f0f29b8ae0e23c3d, https://github.com/spring-projects/spring-framework/commit/568a0b5b79ab170d5f9ea96df43ea9560f93f9ec
0 votes, 5 watchers
Comment From: spring-projects-issues
Sébastien Deleuze commented
Good catch, I have raised the point on Kotlin issue tracker (see this KT-5464 comment) cc Rossen Stoyanchev.
Comment From: spring-projects-issues
Mikhail Konovalov commented
I'm not sure the problem is with Kotlin compiler. Maybe we can avoid somehow wildcard recursive type params?
Comment From: spring-projects-issues
Mikhail Konovalov commented
Sébastien Deleuze, I've used your approach you've mentioned in KT-5464 and it started to compile
@Test
fun `test get 3`() {
val bar: BodySpec<Person, *> = client.get().uri("/person/27").exchange()
.expectBody(Person::class.java).consumeWith { person -> Assert.assertTrue(true) } // compile but leads to NPE
}
But if I generate Kotlin bytecode and then decompile it I get
@Test
public final void test_get_3/* $FF was: test get 3*/() {
this.client.get().uri("/person/27", new Object[0]).exchange().expectBody(Person.class).consumeWith((Consumer)null.INSTANCE);
throw null;
}
(also updated the gist with new case )
Comment From: spring-projects-issues
Sébastien Deleuze commented
Latest comment seems to show this is an issue on Kotlin side that could be fixed in an upcoming Kotlin major version. They also suggest a workaround expectBody.isEqualTo<Nothing?>(Person("42", "Ivan"))
. They are going to provide a roadmap shortly.
Comment From: spring-projects-issues
Mikhail Konovalov commented
Looks like
interface BodySpec<B, S extends BodySpec<B, S>>
Comment From: spring-projects-issues
Sébastien Deleuze commented
Not sure, do you have a alternative proposal?
It seems they plan to fix this in Kotlin 1.2 or 1.3, so without any concrete proposal / PR to discuss with Rossen Stoyanchev, I tend to think we should not enforce artificially another design for a temporary Kotlin issue.
Comment From: spring-projects-issues
Mikhail Konovalov commented
I agree not to break everything for Kotlin issue. But for me it looks not perfect in Java way too. I mean this
BodySpec<B, ?>
It looks like we create strict recursive structure in interface and then relax it with wildcards. I'll try to think of the proposal, but I think it'd be too late to change considering GA.
Comment From: spring-projects-issues
Sébastien Deleuze commented
KT-5464 is expected to be fixed for Kotlin 1.3, and is likely to fix the issue described here, so I prefer keep things as they are if Kotlin is the only relevant reason. Please vote for KT-5464 in order to make sure it will remain high priority.
Comment From: spring-projects-issues
Sébastien Deleuze commented
I am reopening this issue since it seems we can provide a workaround for this very annoying issue by providing a Kotlin extension calling .expectBody<String>().returnResult().apply
as demonstrated on https://github.com/sdeleuze/webflux-kotlin-web-tests/.
Comment From: spring-projects-issues
Sébastien Deleuze commented
Resolved by updating the expectBody
Kotlin extension to use a Kotlin compliant API with implementation similar to the Java one based on expectBody(foo::class.java).returnResult()
.
Comment From: spring-projects-issues
Sébastien Deleuze commented
Side note: be aware of the lack of autocomplete on expectBody
Kotlin extension, it has been raised as KT-23834 to JetBrains.
Comment From: cristianprofile
Is there any example how to use interface BodySpec> using WebTestClient with Kotlin similar to expectBodyList?
Comment From: sdeleuze
Please use stackoverflow for such question, thanks.
Comment From: noah-iam
Hey, For the similar issue , I want to share you my piece of code that is giving me same error :
.webFilter<>(myfilter)
. This is saying to give the generic type here.
Error : Type expected
val client: WebTestClient = WebTestClient.bindToWebHandler { Mono.empty() }
.webFilter<>(myfilter)
.build()
Error : Type argument is not within its bounds.
Expected:
Nothing!
Found:
WebFilter!
@sdeleuze can you help me in this .