Quarkus: ExceptionInInitializerError inside a JUnit5 unit test (not a @QuarkusTest) since 1.1.0.CR1

Created on 12 Dec 2019  路  17Comments  路  Source: quarkusio/quarkus

Describe the bug
After migrating to 1.1.0.CR1 from an application running on 1.0.1.Final I have the following error when launching a JUnit5 _raw_ test (a test without using Quakus support via @QuarkusTest).

java.lang.ExceptionInInitializerError
    at org.jboss.resteasy.core.providerfactory.ResteasyProviderFactoryImpl.registerBuiltin(ResteasyProviderFactoryImpl.java:177)
    at org.jboss.resteasy.spi.ResteasyProviderFactory.getInstance(ResteasyProviderFactory.java:127)
    at org.jboss.resteasy.specimpl.AbstractBuiltResponse.getHeaderValueProcessor(AbstractBuiltResponse.java:111)
    at org.jboss.resteasy.specimpl.AbstractBuiltResponse.toHeaderString(AbstractBuiltResponse.java:426)
    at org.jboss.resteasy.specimpl.AbstractBuiltResponse.getHeaderString(AbstractBuiltResponse.java:455)
    at com.adeo.payment.prepaidcard.domain.impl.PrepaidCardServiceImpl.create(PrepaidCardServiceImpl.java:116)
    at com.adeo.payment.prepaidcard.domain.impl.PrepaidCardServiceImplTest.create(PrepaidCardServiceImplTest.java:130)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:132)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:124)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:74)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:35)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:202)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:198)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: java.lang.IllegalStateException: No configuration is available for this class loader
    at io.smallrye.config.SmallRyeConfigProviderResolver.getConfig(SmallRyeConfigProviderResolver.java:89)
    at org.eclipse.microprofile.config.ConfigProvider.getConfig(ConfigProvider.java:107)
    at org.jboss.resteasy.microprofile.config.ResteasyConfigProvider.getConfig(ResteasyConfigProvider.java:11)
    at org.jboss.resteasy.plugins.providers.RegisterBuiltin$5.run(RegisterBuiltin.java:192)
    at org.jboss.resteasy.plugins.providers.RegisterBuiltin$5.run(RegisterBuiltin.java:189)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at org.jboss.resteasy.plugins.providers.RegisterBuiltin.isGZipEnabled(RegisterBuiltin.java:189)
    at org.jboss.resteasy.plugins.providers.RegisterBuiltin.<clinit>(RegisterBuiltin.java:39)
    ... 70 more

Expected behavior
No exception thrown

To Reproduce
I still don't know exactly what happend so it's hard to give a reproducer.

Environment (please complete the following information):

  • Output of uname -a or ver: Linux 5.3.0-24-generic #26-Ubuntu SMP Thu Nov 14 01:33:18 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
  • Output of java -version: openjdk version "11.0.5" 2019-10-15
  • Quarkus version or git rev: 1.1.0.CR1
kinbug

Most helpful comment

What I would love to see in order to close this issue is the following:

  • Investigate on this precise error (that occurs since 1.1), if it can be fixed good, if not, I'm OK with it as I understand the limitations explained here.
  • Povides a convenient way to create a configuration for Test : QuarkusTestUtils.bootstrapConfiguration() for example.
  • Document the caveats that exist for implementing unit tests without @QuarkusTest as it's very common in an existing code base to have some, and document the way to workaround them (using the new QuarkusTestUtils).

Maybe, as the issue seems to be around http related stuff, we need to provides some http test support to be able to easily mock an HttpRequest, an HttpResponse, HttpHeaders, ...

All 17 comments

I'm a bit confused. How do you run the test if not using @QuarkusTest?

Raw JUnit5 test, I mock all dependencies (for a service: all repositories, for a rest endpoint, all services, ...).

According to me, using @QuarkusTest is for integration test, my definition of a unit test is a test testing only one composant without any dependencies ... So this is a best practice to use raw unit test (without any framework support) and mocking everything so all my tests runs in a few hundreds of ms from my IDE ...

@loicmathieu who starts / calls RESTEasy in your test? Do you start Quarkus yourself somehow?

Not starting resteasy nor Quarkus, just plain Java!
I mock everything: my Rest client, my Panache repositories, ...

As I said, it's a unit test, not an integration test, so I did't bootstrap Quarkus and I mock all dependencies of my service.

I think a small reproducer would be welcome. It doesn't have to have a lot of dependencies or whatever, just the gist of it.

Looks like something that did not need mocking, now does. Or at elast your mocking does go as far as org.jboss.resteasy.specimpl.AbstractBuiltResponse.getHeaderString and thus org.jboss.resteasy.core.providerfactory.ResteasyProviderFactoryImpl

Hm, the PrepaidCardServiceImpl class is probably using javax.ws.rs.core.Response which triggers ResteasyProviderFactory which attempts to access MP Config. So it's very likely a SM Config regression. @dmlloyd @kenfinnigan do you have an idea what's changed?

@mkouba I think your analysis of the issue is correct :)
Yes, I use a javax.ws.rs.core.Response and the errors is on response.getHeaderString("Location");

The issue itself can be fixed by installing a config into QuarkusConfigFactory, and calling ConfigProviderResolver.releaseConfig() on any old config still around, before running your tests. There are some examples in various projects, I'll dig one up after I get back from taking my son to the dentist.

@dmlloyd the issue is fixed by this piece of code

    static volatile SmallRyeConfig config;

    @BeforeAll
    public static void setItUp() {
        final SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder();
        builder.addDefaultSources();
        builder.addDiscoveredConverters();
        builder.addDiscoveredSources();
        config = builder.build();
        QuarkusConfigFactory.setConfig(config);
    }

However I will qualify it of a workaround not a fix. And it works without it in 1.0.1.Final.
Do we really expect all users that does unit test without Quarkus support (and I expect they exist some and maybe a lot of them) to apply this workaround ? How will they find it, should we document it ?

Yes, for now at least. The problem is that the tests assume there is some configuration. But what configuration would it be? Not build time, nor run time, because the test is not running under the purview of Quarkus. Therefore the user must take care to manually set up a configuration; letting the config auto-create encourages leakage (I fixed up a number of these already, which is what led me to the more strict behavior). Leakage impacts testing because the changing the order of test execution can result in different test results if there is more than one configuration in play.

So, manually it can be done via QuarkusConfigFactory as above, or via calls to ConfigProviderResolver to install and remove the configuration, whichever is simpler for the given case.

What I would love to see in order to close this issue is the following:

  • Investigate on this precise error (that occurs since 1.1), if it can be fixed good, if not, I'm OK with it as I understand the limitations explained here.
  • Povides a convenient way to create a configuration for Test : QuarkusTestUtils.bootstrapConfiguration() for example.
  • Document the caveats that exist for implementing unit tests without @QuarkusTest as it's very common in an existing code base to have some, and document the way to workaround them (using the new QuarkusTestUtils).

Maybe, as the issue seems to be around http related stuff, we need to provides some http test support to be able to easily mock an HttpRequest, an HttpResponse, HttpHeaders, ...

  • Povides a convenient way to create a configuration for Test : QuarkusTestUtils.bootstrapConfiguration() for example.

Hi @loicmathieu @gsmet @dmlloyd we have https://github.com/quarkusio/quarkus/issues/5700 which when when fixed will address this point. It will come with documentation as a bonus. I self assigned the issue but as I am on PTO, I have not had the time to look at it implementing it. @loicmathieu or anyone can feel free to give it a go based on the code snippet @dmlloyd shared. Thanks

I'm having the same issue and thus not updating to 1.1.1.Final yet. IMO it's crucial to run plain unit tests that can access the regular JAX-RS client without using a config creation workaround and without switching the JAX-RS impl to Jersey or else.

Maybe some tests on running components such as the JAX-RS client (ClientBuilder...) standalone would help mitigating this in the future?

Are there any updates on this? Happy to help in any way, btw.

@machi1990 Thanks, using 1.3.0.Alpha1 fixes this for me (running JAX-RS client in plain tests works again :tada:)

This fixes also the issue for me, closing it.

Was this page helpful?
0 / 5 - 0 ratings