Skip to content
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

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

Closed
spring-issuemaster opened this issue Jun 22, 2017 · 11 comments

Comments

@spring-issuemaster
Copy link
Collaborator

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:

  • #20606 Unable to use WebTestClient with mock server in Kotlin

Referenced from: commits 91c8b62, 568a0b5

0 votes, 5 watchers

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jun 23, 2017

Sébastien Deleuze commented

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

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jun 23, 2017

Mikhail Konovalov commented

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

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jun 23, 2017

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-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jul 3, 2017

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-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jul 3, 2017

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-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jul 3, 2017

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-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jul 3, 2017

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-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jul 25, 2017

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-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 16, 2018

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-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 17, 2018

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-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 17, 2018

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.