quarkus-cxf
quarkus-cxf copied to clipboard
Testing: using @InjectMock with @CXFClient leads to NPE
Given example code that uses a generated ExamplePortType
(any should suffice):
import com.example.generated.ExamplePortType;
import io.quarkiverse.cxf.annotation.CXFClient;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectMock;
@QuarkusTest
class ExamplePortTypeInjectMockTest {
@InjectMock
@CXFClient
ExamplePortType examplePortTypeMock;
@Test
void testName() {
// nothing to do
}
}
Executing this test should be okay, but it leads to a NPE:
org.junit.jupiter.api.extension.TestInstantiationException: Failed to create test instance
at io.quarkus.test.junit.QuarkusTestExtension.initTestState(QuarkusTestExtension.java:828)
at io.quarkus.test.junit.QuarkusTestExtension.interceptTestClassConstructor(QuarkusTestExtension.java:792)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.api.extension.InvocationInterceptor.interceptTestClassConstructor(InvocationInterceptor.java:72)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:77)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestClassConstructor(ClassBasedTestDescriptor.java:342)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateTestClass(ClassBasedTestDescriptor.java:289)
at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.instantiateTestClass(ClassTestDescriptor.java:79)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:267)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$2(ClassBasedTestDescriptor.java:259)
at java.base/java.util.Optional.orElseGet(Optional.java:369)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$3(ClassBasedTestDescriptor.java:258)
at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:101)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:100)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:65)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$1(NodeTestTask.java:111)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:111)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:79)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
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:143)
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:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
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:143)
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:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
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.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:84)
at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:98)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:40)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:768)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
Caused by: java.lang.reflect.InvocationTargetException
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 io.quarkus.test.junit.QuarkusTestExtension.initTestState(QuarkusTestExtension.java:820)
... 63 more
Caused by: java.lang.NullPointerException
at io.quarkiverse.cxf.CxfClientProducer.selectorCXFClientInfo(CxfClientProducer.java:181)
at io.quarkiverse.cxf.CxfClientProducer.loadCxfClient(CxfClientProducer.java:58)
at com.example.generated.ExamplePortTypeCxfClientProducer.createService(ExamplePortTypeCxfClientProducer.zig:39)
at com.example.generated.ExamplePortTypeCxfClientProducer_ProducerMethod_createService_e6a6e5534d8080ede4e8215852f246f21a2a3dfb_Bean.create(ExamplePortTypeCxfClientProducer_ProducerMethod_createService_e6a6e5534d8080ede4e8215852f246f21a2a3dfb_Bean.zig:210)
at com.example.generated.ExamplePortTypeCxfClientProducer_ProducerMethod_createService_e6a6e5534d8080ede4e8215852f246f21a2a3dfb_Bean.get(ExamplePortTypeCxfClientProducer_ProducerMethod_createService_e6a6e5534d8080ede4e8215852f246f21a2a3dfb_Bean.zig:240)
at com.example.generated.ExamplePortTypeCxfClientProducer_ProducerMethod_createService_e6a6e5534d8080ede4e8215852f246f21a2a3dfb_Bean.get(ExamplePortTypeCxfClientProducer_ProducerMethod_createService_e6a6e5534d8080ede4e8215852f246f21a2a3dfb_Bean.zig:275)
at io.quarkus.arc.impl.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:430)
at io.quarkus.arc.impl.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:443)
at io.quarkus.arc.impl.ArcContainerImpl.instanceHandle(ArcContainerImpl.java:413)
at io.quarkus.arc.impl.ArcContainerImpl.instance(ArcContainerImpl.java:222)
at io.quarkus.test.junit.mockito.internal.CreateMockitoMocksCallback.getBeanInstance(CreateMockitoMocksCallback.java:72)
at io.quarkus.test.junit.mockito.internal.CreateMockitoMocksCallback.afterConstruct(CreateMockitoMocksCallback.java:28)
... 68 more
My expactation would be, that a mockito mock of ExamplePortType
is injected into the test and all other beans.
check if InjectionPoint ip is not null in io.quarkiverse.cxf.CxfClientProducer.selectorCXFClientInfo
check if InjectionPoint ip is not null in io.quarkiverse.cxf.CxfClientProducer.selectorCXFClientInfo
@dufoli are you asking @mickroll to check that or is that a note to yourself for implementing the fix? Sorry if that's a stupid question. 😉
Note for implementing fix. But maybe I have to dig deeper to understand why it is null...
can you test this branch. I guess you have an injection point without annotation CxfClient and it make it bug.
another thing, I read that may be the root cause if it do not worked.
https://quarkus.io/guides/cdi it talk about monking and using ApplicationScoped in order to mock instead of singleton. It is what we do but it seems to be used with QuarkusMock quarkus mock Can you switch to
@QuarkusTest
class ExamplePortTypeInjectMockTest {
@Inject
@CXFClient
ExamplePortType examplePortTypeMock;
@BeforeAll
public static void setup() {
ExamplePortType mock = Mockito.mock(ExamplePortType.class);
//Mockito.when(mock.greet("Stuart")).thenReturn("A mock for Stuart");
// mock all needed return here
QuarkusMock.installMockForType(mock, ExamplePortType.class);
}
@Test
void testName() {
// nothing to do
}
}
I'll try that within the next days. Sorry, current project is on a tight schedule.
Finally found some time to try you suggestion.
Can you switch to
With this approach, installMockForType
fails in @BeforeAll
:
javax.enterprise.inject.UnsatisfiedResolutionException: No bean found for required type [interface com.example.ExamplePortType] and qualifiers [[]]
at io.quarkus.arc.impl.InstanceImpl.bean(InstanceImpl.java:175)
at io.quarkus.arc.impl.InstanceImpl.getInternal(InstanceImpl.java:196)
at io.quarkus.arc.impl.InstanceImpl.get(InstanceImpl.java:93)
at io.quarkus.test.junit.QuarkusMock.installMockForType(QuarkusMock.java:57)
at com.example.ExamplePortTypeInjectMockTest.setup(ExamplePortTypeInjectMockTest.java:32)
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 io.quarkus.test.junit.QuarkusTestExtension.runExtensionMethod(QuarkusTestExtension.java:944)
at io.quarkus.test.junit.QuarkusTestExtension.interceptBeforeAllMethod(QuarkusTestExtension.java:734)
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)
Btw. using installMockForType
in @BeforeEach
yields the same problem.
can you test this branch. I guess you have an injection point without annotation CxfClient and it make it bug.
The NPE is gone. But the test still won't run. I'll try to create a reproducer for the rest.
Pushed a demonstrator, see above. This is what i want to achieve:
Service -> cxfWsClient
Test -> Service, with mocked cxfWsClient
Btw. I couldn't get any tests to run, always failing with IllegalStateException: No root definition found for class io.quarkiverse.cxf.CxfConfig
on a fresh master
pull with mvn clean install
.
@mickroll hmm....
what is properties of your service.
https://github.com/mickroll/quarkus-cxf/blob/f75dd786175135ba6466bf9c4aa6e959e2c2c44d/integration-tests/src/main/resources/application.properties
there are few service and they are all named.
can you try with
@CXFClient("mockCalculator")
can you try with
@CXFClient("mockCalculator")
Still fails like every other test with IllegalStateException: No root definition found for class io.quarkiverse.cxf.CxfConfig
.
Somehow my approach for running all integration tests seems to be wrong:
$ git clone ...
$ mvn clean install
-> all tests fail with IllegalStateException: No root definition found for class io.quarkiverse.cxf.CxfConfig
, even on master
.
with:
$ mvn --version
Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d)
Maven home: [...]
Java version: 11.0.11, vendor: AdoptOpenJDK, runtime: [...]
Default locale: de_DE, platform encoding: Cp1252
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
ok got it indeed there is some code on quarkus core to handle injectMock attribute. So we have to handle it too.
https://github.com/quarkusio/quarkus/blob/main/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoMocksCallback.java
commit to introduce support for it: https://github.com/quarkusio/quarkus/commit/96db1d725d689f441bcf674741ae974a3cf2c90b#diff-0f49776425f1d405da03781ad19d8d1f2b935976463c4e8cdb66a4ee8c37ece6
here is class to add the support for it: https://github.com/quarkusio/quarkus/blob/main/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoMocksCallback.java
so the issue is indeed that when you inject your mock it created bean instance first and use the regular ay before overwriting it. But for that we need to have configuration and there are no root node for client configuration.
https://github.com/mickroll/quarkus-cxf/commit/f75dd786175135ba6466bf9c4aa6e959e2c2c44d
SO
CXFClientInfo must be injected with a dummy for mock I guess. Best maybe is to have a default CXFClientInfo with convention properties instead of configuration so no exception thrown. It will fix mock and raise configuration over configuration for default client (localthost url, ... we can add it in: CxfClientProducer.produceCxfClient if we do so we need the patch I cancel to check if injectionPoint is null too
@shumonsharif @famod are you ok for the approach ?
@dufoli I was never able to successfully mock a CXF / JAX-WS client in general (even outside of the Quarkus realm), so I'm not entirely sure how this will work without an actual endpoint to process the request.
I'm also not entirely clear on the requirement ... I'm guessing we are trying to return a pre-canned static response for the mock client, and don't want it to make an HTTP call?
I'm also not entirely clear on the requirement ... I'm guessing we are trying to return a pre-canned static response for the mock client, and don't want it to make an HTTP call?
Yes, maybe i should have made that clearer in the first place. Sorry for that.
What i have is a service that uses an injected CxfClient to connect to an external webservice. Now i want to be able to test all the services logic, without doing the external request. Instead of the CxfClient i want to have a mockito mock injected into the service, so i can check its input and control its output. Just like any service-layer-test where you mock one bean to test another. This way i would also have a very slim (fast) test, without a lot of other dependencies. What i don't want is that a real http request is made to some kind of mock. The latter would require a lot of configuration for (eg. disabling) tls / auth / request logging / ...
I have worked a little bit on it. indeed, we have an issue because
Caused by: java.lang.RuntimeException: org.apache.cxf.jaxws.JaxWsClientProxy@20a81a61 is not a normal scoped CDI bean, make sure the bean is a normal scope like @ApplicationScoped or @RequestScoped. Alternatively you can use '@InjectMock(convertScopes=true)' instead of '@InjectMock' if you would like Quarkus to automatically make that conversion (you should only use this if you understand the implications).
at io.quarkus.test.junit.mockito.internal.SetMockitoMockAsBeanMockCallback.installMock(SetMockitoMockAsBeanMockCallback.java:18)
And the issue is that sei (web service interface is not ApplicationScoped. And this class is generated by cxf not by quarkus. On resteasy, I found a bug with same pattern and they fixed it by added @AppicationScoped on interface: https://github.com/quarkusio/quarkus/issues/8622 but because it is done on cxf part, I do not think we can do that. Or maybe by enriching class generated by cxf. I have tried to :
- add org.mockito.plugins.MockMaker resource file without success
- add @InjectMock(convertScopes = true) without success
Hi @mickroll @famod @dufoli I may have put together a fairly trivial workaround to address this; it leverages custom injection of fields into the test classes, and is generic enough to be applied to mock any CXFClient instance.
Would you kindly share any thoughts on this; whether it is a feasible workaround? Does it address the core concerns?
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectMockCXFClient {
}
import io.quarkus.test.common.QuarkusTestResourceConfigurableLifecycleManager;
import org.mockito.Mockito;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Map;
public class MockCXFClientTestResource implements QuarkusTestResourceConfigurableLifecycleManager {
@Override
public Map<String, String> start() {
return Collections.emptyMap();
}
@Override
public void stop() {
}
@Override
public void inject(Object testInstance) {
Class<?> c = testInstance.getClass();
while (c != Object.class) {
for (Field f : c.getDeclaredFields()) {
Annotation ano = null;
for (Annotation annotation : f.getAnnotations()) {
if (InjectMockCXFClient.class.getName().equals(annotation.annotationType().getName())) {
ano = annotation;
}
}
if (ano != null) {
f.setAccessible(true);
try {
f.set(testInstance, Mockito.mock(f.getType()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
c = c.getSuperclass();
}
}
}
@QuarkusTest
@QuarkusTestResource(MockCXFClientTestResource.class)
public class CalculatorSoapTest {
@InjectMockCXFClient
CalculatorSoap mockCalculatorSoap;
@BeforeEach
void setup() {
doReturn(23).when(mockCalculatorSoap).subtract(anyInt(), anyInt());
doReturn(23).when(mockCalculatorSoap).divide(anyInt(), anyInt());
doReturn(23).when(mockCalculatorSoap).add(anyInt(), anyInt());
doReturn(23).when(mockCalculatorSoap).multiply(anyInt(), anyInt());
}
@Test
void testMultiply() {
Assertions.assertEquals(23, mockCalculatorSoap.multiply(13, 17));
}
@Test
void testAdd() {
Assertions.assertEquals(23, mockCalculatorSoap.add(19, 23));
}
}
@shumonsharif Thanks for taking a look!
I haven't tried it out, but I don't think it would replace the respective client in other classes than the test class?
Imagine a Connector
class in main
that gets the client injected and that provides some coarser grained methods, e.g. to avoid soap/jaxb binding classes from leaking into the core domain of your app. What you typically want to do (well, we do) is to test the Connector
, but with a mocked client.
Two more remarks:
-
@QuarkusTestResource
runs for all tests (might be ok or not) and@QuarkusTestResource.restrictToAnnotatedClass
triggers a Quarkus reboot (runtime penalty) - Since a few Quarkus versions,
QuarkusTestResourceLifecycleManager
has a more convenientvoid inject(TestInjector testInjector)
Makes sense @famod; what I proposed would only work for simper use cases, such as injecting respective clients directly into the test classes. Will definitely need to give this some deeper thought - as @dufoli mentioned previously, CXF is generating the classes we'd need to enrich, which just seems cumbersome.
@famod Interestingly enough, I just tried out the use case you described using @InjectMocks
and it appears to work with the proposed workaround. Assuming we have the following class:
public class CalculatorService {
@Inject
@CXFClient("calculator-soap12-client")
CalculatorSoap calculatorSoap2;
public int add(int intA, int intB) {
return calculatorSoap2.add(intA, intB);
}
}
The following test case works with the mocked CXFClient
(I was not expecting it to previously due to the custom @InjectMockCXFClient
annotation):
@QuarkusTest
@QuarkusTestResource(MockCXFClientTestResource.class)
public class CalculatorSoapTest {
@InjectMockCXFClient
@CXFClient("calculator-soap12-client")
CalculatorSoap mockCalculatorSoap2;
@InjectMocks
CalculatorService calculatorService = new CalculatorService();
@BeforeEach
void setup() {
MockitoAnnotations.openMocks(this);
doReturn(23).when(mockCalculatorSoap2).add(anyInt(), anyInt());
}
@Test
void testAdd() {
Assertions.assertEquals(23, calculatorService.add(2, 3));
}
}
Would you kindly take a look once you have some time? I think documenting this simple workaround should suffice assuming the @InjectMocks
will address the use case you were describing?
I should have been more precise:
Yeah, when doing the injection "manually", this will work:
@InjectMocks
CalculatorService calculatorService = new CalculatorService();
What I meant won't work is:
@Inject
CalculatorService calculatorService;
Here, ArC will use the real client, not the mocked one (because it doesn't know about the mock).
The @InjectMocks
workaround can become more complicated/verbose when there are other non-mocked beans that need to be injected into CalculatorService.
Furthermore, calculatorService
will not receive any interceptor bindings etc. when not created via ArC (obviously, but this might not be clear to every user).
It's a workaround that is worth documenting, but has a few drawbacks (as is the nature of workarounds) that should be mentioned.
I need to take more time to have a longer look, but it appears to me that we need the InjectionPoint
(which is forcing us to use @Dependent
) "only" to detect wheter @CXFClient
is present: https://github.com/quarkiverse/quarkus-cxf/blob/70838322872be170ce2942901989ac2d264499fc/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java#L181-L183
So I've been thinking: What if we collect that info during build time and then produce an @ApplicationScoped
(!) bean per found @CXFClient
?
Of course, this would be a rather big change with some implications.
Hey @famod I think this may work, and possibly our best option - it is also indeed a major change with impacts to existing functionality. I will also try to dig deeper based on your suggestion.
I believe impact wise with switching to @ApplicationScoped
we may need to watch out for some of the issues described here:
https://cxf.apache.org/faq.html#FAQ-AreJAX-WSclientproxiesthreadsafe%3F
Were there any other possible implications you were thinking?
Yes, thread-safety is one concern. I actually used thread.local.request.context
for a long time in a previous project.
Another implication is that unwrapping the client proxy and modifying e.g. the Conduit in a @PostConstruct
of one bean will then have a global effect. Of course, this isn't relevant if the client is only injected in one place.
Speaking of ClientProxy, I suppose people would need to unwrap the ArC proxy first, and only then use ClientProxy.getClient()
. If that's the case, documentation needs to reflect that and ideally a small util should be provided to make things easier.
Finally, that change would close the door for "injection point specific settings". Or let's say...it would make it much more difficult.
In a previous project (based on JBoss EAP) @mickroll and I developed a small SOAP client producer framework that allowed a couple of settings per injection point and it worked rather well. All clients were @Dependent
(obviously).
At some point, to save memory and creation time, we introduced a static cache to produce/reuse the same client instance for each injection point that had the same config as another one. So this is doable, but you need to properly choose all the config key parts and the end this was actually undermining the @Dependent
contract, TBH.
Any progress on this issue ?
@ppalaga WDYT about this, especially https://github.com/quarkiverse/quarkus-cxf/issues/248#issuecomment-982103369 (and the following two comments)?
Before I can help, I'd need some help understanding what's the intention behind the reported use case. I also have not had time to read the whole discussion.
I can understand that @mickroll wants to use a mock of ExamplePortType in his tests. He does not want Quarkus to fail at boot with a NPE. I am rather inexperienced in using mocks. I know little about what is @InjectMock
supposed to be doing. I assume the life cycle of @InjectMock
-annotated fields is managed by some kind of Mockito JUnit extension (please correct me if I am wrong). But where is the behavior of the mock defined? - perhaps it will be implemented in the test method? Nevermind, if the lifecycle of the client is supposed to be managed by Mockito, then what is the point of adding the @CXFClient
annotation? I'd say you can want one of them, but not both. @CXFClient
will make the lifecycle of the client to be owned by Quarkus CXF extension which, by its very nature cannot inject a mock.
@ppalaga For a better understanding of the use case please read my comment https://github.com/quarkiverse/quarkus-cxf/issues/248#issuecomment-869404306
I also added an example implementation of that use case as a reproducer, you can see the link right above the mentioned comment.
Do you have any updates about this issue? Will we be able to mock @CXFClient?
Closing, not needed anymore. Can't invest time, either.