Skip to content

Kotlin unable to inherit type for WebTestClient#BodySpec [SPR-15692] #20251

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
spring-projects-issues opened this issue Jun 22, 2017 · 14 comments
Assignees
Labels
in: test Issues in the test module in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Jun 22, 2017

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 <T : Nothing!> 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:

Referenced from: commits 91c8b62, 568a0b5

0 votes, 5 watchers

@spring-projects-issues
Copy link
Collaborator Author

Sébastien Deleuze commented

Good catch, I have raised the point on Kotlin issue tracker (see this KT-5464 comment) cc Rossen Stoyanchev.

@spring-projects-issues
Copy link
Collaborator Author

Mikhail Konovalov commented

I'm not sure the problem is with Kotlin compiler.
Maybe we can avoid somehow wildcard recursive type params?

@spring-projects-issues
Copy link
Collaborator Author

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 )

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

Mikhail Konovalov commented

Looks like <Noting?> fixes NPE but call chain will be finished on this.
Don't you think it can be fixed if redesign this

interface BodySpec<B, S extends BodySpec<B, S>>

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

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/.

@spring-projects-issues
Copy link
Collaborator Author

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().

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues spring-projects-issues added in: test Issues in the test module type: enhancement A general enhancement in: web Issues in web modules (web, webmvc, webflux, websocket) labels Jan 11, 2019
@spring-projects-issues spring-projects-issues added this to the 5.0.6 milestone Jan 11, 2019
@cristianprofile
Copy link

Is there any example how to use interface BodySpec<B, S extends BodySpec<B, S>> using WebTestClient with Kotlin similar to expectBodyList?

@sdeleuze
Copy link
Contributor

Please use stackoverflow for such question, thanks.

@noah-iam
Copy link

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 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: test Issues in the test module in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

4 participants