Quarkus: Retrieving dependencies from CDI in tests that implement interface throws java.lang.IllegalStateException if code defined outside JUnit test

Created on 3 Mar 2020  路  14Comments  路  Source: quarkusio/quarkus

Describe the bug
When attempting to inject a dependency that implements an interface, if you use the class rather than the interface it throws a java.lang.IllegalStateException whereas using the interface is fine.

This only seems to be an issue if you try to make a "helper" method that lives outside of a @Test function.

This is also not a problem if you're injecting a class that doesn't implement an interface.

Expected behavior
You should be able to inject either the interface or a specific implementation, and creating a helper method outside of a JUnit test should be fine for either.

Actual behavior
I have an interface called TestInterface, and an implementation called TestInterfaceImpl and the TestInterfaceImpl is annotated with @ApplicationScoped.

I have a series of tests that inject this dependency in order to call various methods on the dependency, using the context for the rest of the DI. If I attempt to retrieve the dependency within a JUnit test (annotated with @Test) then both of these methods work:

    @Test
    fun testOne() {
        val dep = CDI.current().select(TestInterface::class.java).get()

        // Do some stuff - this works
    }
    @Test
    fun testTwo() {
        val dep = CDI.current().select(TestInterfaceImpl::class.java).get()

        // Do some stuff - this works
    }

I have ended up repeating this across all of my tests, so wanted to take this out to a helper function. This sits within the class, but is not part of any @Test function:

    private fun getDependency() = CDI.current().select(TestInterface::class.java).get()

    @Test
    fun testThree() {
        val dep = getDependency()

        // Do some stuff - this works
    }

This above function is fine, but the following does not work and results in the below stack trace.

    private fun getDependency() = CDI.current().select(TestInterfaceImpl::class.java).get()

    @Test
    fun testFour() {
        val dep = getDependency()

        // This doesn't work :(
    }

Stack trace:

Caused by: java.lang.NoSuchMethodError: com.example.TestInterfaceImpl: method <init>()V not found
    at com.example.TestInterfaceImpl_ClientProxy.<init>(EngineerSummaryServiceImpl_ClientProxy.zig:21)
    at com.example.TestInterfaceImpl_Bean.<init>(EngineerSummaryServiceImpl_Bean.zig:118)
    at io.quarkus.arc.setup.Default_ComponentsProvider.addBeans2(Default_ComponentsProvider.zig:1140)
    at io.quarkus.arc.setup.Default_ComponentsProvider.getComponents(Default_ComponentsProvider.zig:43)
    at io.quarkus.arc.impl.ArcContainerImpl.<init>(ArcContainerImpl.java:102)
    at io.quarkus.arc.Arc.initialize(Arc.java:20)
    ... 82 more

Unable to locate CDIProvider
java.lang.IllegalStateException: Unable to locate CDIProvider
    at javax.enterprise.inject.spi.CDI.findAllProviders(CDI.java:121)
    at javax.enterprise.inject.spi.CDI.getCDIProvider(CDI.java:82)
    at javax.enterprise.inject.spi.CDI.current(CDI.java:64)
    ...

This is also _not_ a problem if you're injecting a class that doesn't implement an interface.

To Reproduce
Here's a reproducer: https://github.com/bnjns/quarkus-bug-7541

Just run

$ ./gradlew clean test

Configuration
N/A

Screenshots
N/A

Environment (please complete the following information):

  • Output of uname -a or ver: Darwin Beans.local 19.3.0 Darwin Kernel Version 19.3.0: Thu Jan 9 20:58:23 PST 2020; root:xnu-6153.81.5~1/RELEASE_X86_64 x86_64
  • Output of java -version: openjdk version "1.8.0_232"
  • GraalVM version (if different from Java): GraalVM CE 19.2.1 (build 25.232-b07-jvmci-19.2-b03, mixed mode)
  • Quarkus version or git rev: 1.2.1.Final

Additional context
Using Kotlin 1.3.61, compiled down to Java 1.8
Using Gradle (6.0.1)

arearc arekotlin kinbug

Most helpful comment

Thanks for reporting!

This one looks like a lot of fun :)
We would definitely need a reproducer for this, so when you get some time, it would be wonderful if you could create one and add it to the description

All 14 comments

Thanks for reporting!

This one looks like a lot of fun :)
We would definitely need a reproducer for this, so when you get some time, it would be wonderful if you could create one and add it to the description

@geoand I do like to make your lives interesting! And I must admit it took me a while to track it down to the class/interface issue so looks like it might be a bit of a challenge 馃槀

Here's a reproducer, which I've added to the description: https://github.com/bnjns/quarkus-bug-7541

Thanks a lot!

So I took a look at this and you can easily get past the problem by changing getService to:

private fun getService(): ServiceOne = CDI.current().select(ServiceOneImpl::class.java).get()

in TestingUsingImplementationWithHelperMethod.

I am not absolutely sure what the problem is, but my guess is that it has to do with Kotlin's non-nullable types.

Since the workaround is so easy I don't think it warrants any further exploration and I'll go ahead and close this issue. Feel free to reopen if you feel otherwise

Hmm, I see what you mean - making sure you explicitly state the return type as the interface rather than letting Kotlin infer the type as the implementation?

For me, I don't think this workaround does work. My particular use case was possible wanting to test a "helper" method on the implementation that isn't declared on the interface - so you need to inject the implementation and use that as the return type.

I'll reopen since the issue probably goes deeper than I originally posted.

The even weirder thing is that a subsequent ./gradlew test (after the first one) passes... Also the tests seem to pass when run from the IDE.

Since this has a lot of moving parts and there are workarounds, I probably won't spend any time on it unless / until I get some downtime.
In the meantime, anyone else if free to take a stab at it :)

The even weirder thing is that a subsequent ./gradlew test (after the first one) passes... Also the tests seem to pass when run from the IDE.

Yep, we've had this problem so many times 馃槀

I'm happy using a workaround for now, but would be grand if this could get tackled at some point 馃檪

I've found that this issue seems to go even further - if you have a class that uses a combination of dependencies that are classes or interfaces you get the same problem.

So say you have DepA and DepB and DepA is a class, DepB is an interface for which there is an implementation DepBImpl. If you create something that uses these, ConsumerImpl and depend on DepA and DepB you get the same problem, _unless_ you make ConsumerImpl implement an interface (Consumer) and use

CDI.current().select(Consumer::class.java).get()

I'll try to find some time to add this to the reproducer this evening.


Full example that fails:

@ApplicationScoped
class DepA

interface DepB {
}

@ApplicationScoped
class DepBImpl : DepB

@ApplicationScoped
class ConsumerImpl(
    private val depA: DepA,
    private val depB: DepB
)
private fun getConsumer(): ConsumerImpl = CDI.current().select(ConsumerImpl::class.java).get()

Full example that works:

@ApplicationScoped
class DepA

interface DepB {
}

@ApplicationScoped
class DepBImpl : DepB

interface Consumer {
}

@ApplicationScoped
class ConsumerImpl(
    private val depA: DepA,
    private val depB: DepB
) : Consumer
private fun getConsumer(): Consumer = CDI.current().select(Consumer::class.java).get()

This gradle/kotlin combo is really "nutritious". I'm not a kotlin expert but I noticed few details. First of all your @ApplicationScoped services do not declare a no-args constructor and so quarkus has to enhance the bytecode to overcome a limitation of CDI client proxies as described in the docs. And so if we see NoSuchMethodError it's very likely a classloading issue - quarkus attempts to work with non-enhanced bytecode. Also Unable to locate CDIProvider indicates that ArcCDIProvider was set to the static field of a different javax.enterprise.inject.spi.CDI class.

Interesting. I guess we really should be using the noarg plugin to do that during build automatically for things like @ApplicationScoped services.

I'll give that a try and see if it makes a difference.

Edit: Turns out it makes no difference :(

I just tested the reproducer out on 1.3.0.CR2 and it seems to work.

Yes, indeed. That confirms my theory of a class loading issue. @bnjns could you try to upgrade to 1.3.0.CR2?

@mkouba Yep, just tested it with 1.3.0CR2 and it works, thanks 馃檪

Thanks for checking, closing

Was this page helpful?
0 / 5 - 0 ratings