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

Mocked bean created via method is not injected in another bean #26733

Closed
vadim-gleif opened this issue Jul 14, 2022 · 24 comments · Fixed by #27179
Closed

Mocked bean created via method is not injected in another bean #26733

vadim-gleif opened this issue Jul 14, 2022 · 24 comments · Fixed by #27179
Labels
area/testing kind/bug Something isn't working
Milestone

Comments

@vadim-gleif
Copy link

Describe the bug

Beans which are created via @Produces api are not injected in others beans fields. It works fine if I annotate bean with @ApplicationScope instead of using @Produces.

Expected behavior

Mocked bean in passed to other beans.

Actual behavior

Real bean in passed to other beans.

How to Reproduce?

example repo: https://github.com/vadim-hleif/test-quarkus-mocking

Output of uname -a or ver

Darwin Vadims-MacBook-Pro.local 20.6.0 Darwin Kernel Version 20.6.0: Tue Oct 12 18:33:42 PDT 2021; root:xnu-7195.141.8~1/RELEASE_X86_64 x86_64

Output of java -version

openjdk 17.0.3 2022-04-19 LTS OpenJDK Runtime Environment Zulu17.34+19-CA (build 17.0.3+7-LTS) OpenJDK 64-Bit Server VM Zulu17.34+19-CA (build 17.0.3+7-LTS, mixed mode, sharing)

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.10.2

Build tool (ie. output of mvnw --version or gradlew --version)

7.4.2

Additional information

No response

@vadim-gleif vadim-gleif added the kind/bug Something isn't working label Jul 14, 2022
@quarkus-bot
Copy link

quarkus-bot bot commented Jul 14, 2022

/cc @geoand

@glefloch
Copy link
Member

The default CDI bean scope is dependant. It's not possible to mock those beans so I think we won't be able to do something for this case. Maybe @geoand has an idea?

@vadim-gleif
Copy link
Author

The default CDI bean scope is dependant. It's not possible to mock those beans so I think we won't be able to do something for this case. Maybe @geoand has an idea?

Didn't get the point, default is dependant, but I specified applicationScoped, isn't it?
Also if I change scope of bean B to dependant it fails.
Invalid use of io.quarkus.test.junit.mockito.InjectMock - the injected bean does not declare a CDI normal scope but: javax.enterprise.context.Dependent.

@glefloch
Copy link
Member

I miss read your comment, I though it was working when annotating with both @Produce and @ApplicationScoped

@geoand
Copy link
Contributor

geoand commented Jul 15, 2022

It seems like your reproducer mixes Quarkus and Mockito annotations and also uses Kotlin to it's very hard to understand what is going on.

I tried something with a sample Java project and it worked just fine.

@vadim-gleif
Copy link
Author

It seems like your reproducer mixes Quarkus and Mockito annotations and also uses Kotlin.

I removed 1 Mockito annotation, no changes.

it's very hard to understand what is going on.

There are two beans: A and B, B is injected in A, B is created via @Produses , A class itself annotated by @ApplicationScoped, B instance can't be mocked in tests (if I create bean B in the same way as A created it works). I did everything in the same way as it described there.
Looks like the problem with @Produses method to create beans.
If I remove this Mockito extension it doesn't work at all (B will be in a corrupted state, looks like something wrong with proxies and not null field with default value is null)
class B(private val configs: SomeConfigs = SomeConfigs()) {...
Cannot invoke "test.quarkus.mocking.SomeConfigs.getRetryLimit()" because "this.configs" is null

I tried something with a sample Java project and it worked just fine.

doesn't Quarkus support Kotlin? Didn't get the point. https://quarkus.io/guides/kotlin

@geoand
Copy link
Contributor

geoand commented Jul 15, 2022

Quarkus does support Kotlin, of course.

My point was that your sample was convoluted so it's hard to pinpoint what the problem is and as I said, a sample Java application I tried with a mock for a bean created via a @Produces method worked just fine

@vadim-gleif
Copy link
Author

I see.

I have updated example (add java sample, unified java and kotlin codebase). Yes, java example works fine, looks like something wrong with proxy creation in kotlin example.

Will try to solve on my own but it would be perfect you someone also could take a look.

Thanks.

@geoand
Copy link
Contributor

geoand commented Jul 15, 2022

Does a simple Kotlin project with a single @Produces bean that is mocked work properly?

@vadim-gleif
Copy link
Author

vadim-gleif commented Jul 15, 2022

Yes, if bean is not used anywhere else - it works. Example:
https://github.com/vadim-hleif/test-quarkus-mocking/blob/master/src/test/kotlin/test/quarkus/mocking/MockingTest.kt#L24
So, here we have mocked bean

But as it's shown in the test class, when this bean is used in another bean, mock is not injected there (it's relevant only for Kotlin; there is a corrupted version of the bean, even not nullable fields are null, this partially fixes it, instead of corrupted bean there is a normal instance (not a mock)).

@geoand
Copy link
Contributor

geoand commented Jul 26, 2022

Something is wrong with your code and it's not Quarkus.

Even doing this:

open class Engine {

    fun launch(retryTimes: Int): String {
        println("going to retry ${retryTimes + 2} times")

        return "ready"
    }

}
internal class MockingTest {

    @Test
    fun `two beans, one is created via producer, second one annotated -- it should work`() {
        var engine = mock<Engine>()
        whenever(engine.launch(any())).thenReturn("not ready")
    }
}

fails.

So I am going to close this.

@geoand geoand closed this as not planned Won't fix, can't repro, duplicate, stale Jul 26, 2022
@geoand geoand added the triage/invalid This doesn't seem right label Jul 26, 2022
@vadim-gleif
Copy link
Author

what do you mean?

image

Ok, if example with two empty classes and only Quarkus dependencies is "bad case" for you, I will try to find out Quarkus bug on my own.

@geoand
Copy link
Contributor

geoand commented Aug 3, 2022

My point is that the super simple Kotlin example I added above fails without running a Quarkus test, just a plain junit test

@vadim-gleif
Copy link
Author

It doesn't fail, I send a screenshoot. Also I've pushed example without using Quarkus CDI in the same file, it works.
Did you use master branch?

@vadim-gleif
Copy link
Author

But making all classes as open resolves this issue, sorry, you are right.

@vadim-gleif
Copy link
Author

Could you help with one more question? It still looks like there is an issue with Quarkus.

The problem with example above is final kotlin classes, as I said if I mark classes/method with I going to mock as open everything work, but in some cases I can't do it - e.g. when I want to use in CDI third-party class which is final and create it via @Produces API.

Mockito suggest using this option. When I add it example without Quarkus CDI works correctly, but with Quarkus CDI it doesn't (looks like there are two different instances (injected and test and injected in beans).
Isn't it a bug? Or just some unsupported feature.

Are there other ways to deal with the case which I mentioned? When I want to mock final class and I can't mark it as open directly or via all-open plugin (third party api).

Thank you.

p.s. This mockito option changes behaviour, without it quarkus can't mock final class and fails:

Cannot mock/spy class test.quarkus.mocking.Engine
Mockito cannot mock/spy because :

- final class

With enabled option bean is mocked, but as it shown there mockito API doesn't, work (if I mark mocked method as open it works with quarkus too).

@geoand
Copy link
Contributor

geoand commented Aug 3, 2022

In order to be convinced that there is a Quarkus issue I'll need to see a very similar test that works as plain JUnit test and does not work when using @QuarkusTest.

At this point with all the things you mention I don't know what you expect me to test. So for me to look at this any more, I'll need to see (in your project) what I mentioned above.

@vadim-gleif
Copy link
Author

So,

This class has both cases:

  1. Using Quarkus CDI, it doesn't work.
  2. Pure Junit, it works

I also created branch with Spring DI, it works with the same setup (kotlin + final class + enabled mockito feature to work with final classes).

Just to summarise:

  • There is class Car
  • There is class Engine
  • Engine is injected in Car, Engine.launch just returns string, ready (default)
  • Engine is created via method api and it's final, in some cases it's impossible to mark class as open, e.g. - third party dependency. This mockito feature allows mock even final classes.
  • With this Quarkus API i can't mock Engine.launch result to not ready, because in the test and in Car I see different instances of Engine. Looks like @InjectMock doesn't work properly in such a case.

p.s. if try to mock not final class it works fine.

@geoand
Copy link
Contributor

geoand commented Aug 5, 2022

Okay, I'll have a look next week.

@geoand
Copy link
Contributor

geoand commented Aug 8, 2022

It seems to me we are going in circles as I am getting the same exact errors I mentioned earlier with the latest version of your code.

Even if I reduce things even further and have a simple test class like:

internal class MockingTest {

    private  val engineManuallyCreated: Engine = mock()

    @Test
    fun `no Quarkus CDI -- OK`() {
        whenever(engineManuallyCreated.launch(any())).thenReturn("not ready")
    }
}

then when I run the test I get:

org.mockito.exceptions.base.MockitoException: 
Cannot mock/spy class test.quarkus.mocking.Engine
Mockito cannot mock/spy because :
 - final class
	at app//test.quarkus.mocking.MockingTest.<init>(MockingTest.kt:62)
	at java.base@17.0.2/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base@17.0.2/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base@17.0.2/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base@17.0.2/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base@17.0.2/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at app//org.junit.platform.commons.util.ReflectionUtils.newInstance(ReflectionUtils.java:550)
	at app//org.junit.jupiter.engine.execution.ConstructorInvocation.proceed(ConstructorInvocation.java:56)
	at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at app//io.quarkus.test.junit.QuarkusTestExtension.interceptTestClassConstructor(QuarkusTestExtension.java:734)
	at app//org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at app//org.junit.jupiter.api.extension.InvocationInterceptor.interceptTestClassConstructor(InvocationInterceptor.java:73)
	at app//org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at app//org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at app//org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:77)
	at app//org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestClassConstructor(ClassBasedTestDescriptor.java:355)
	at app//org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateTestClass(ClassBasedTestDescriptor.java:302)
	at app//org.junit.jupiter.engine.descriptor.ClassTestDescriptor.instantiateTestClass(ClassTestDescriptor.java:79)
	at app//org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:280)
	at app//org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$4(ClassBasedTestDescriptor.java:272)
	at java.base@17.0.2/java.util.Optional.orElseGet(Optional.java:364)
	at app//org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$5(ClassBasedTestDescriptor.java:271)
	at app//org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31)
	at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:102)
	at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:101)
	at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:66)
	at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$2(NodeTestTask.java:123)
	at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:123)
	at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:90)
	at java.base@17.0.2/java.util.ArrayList.forEach(ArrayList.java:1511)
	at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base@17.0.2/java.util.ArrayList.forEach(ArrayList.java:1511)
	at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at app//org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at app//org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
	at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at app//org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at app//org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at app//org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at app//org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
	at java.base@17.0.2/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base@17.0.2/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base@17.0.2/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base@17.0.2/java.lang.reflect.Method.invoke(Method.java:568)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at jdk.proxy1/jdk.proxy1.$Proxy2.stop(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
	at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
	at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
	at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)

So I really don't see the inline mocket coming into play

@geoand
Copy link
Contributor

geoand commented Aug 8, 2022

I just figured out why we are seeing different results. This file has the wrong casing, which a Mac doesn't matter, but in Linux does...

@geoand geoand reopened this Aug 8, 2022
@quarkus-bot quarkus-bot bot removed the triage/invalid This doesn't seem right label Aug 8, 2022
@vadim-gleif
Copy link
Author

vadim-gleif commented Aug 8, 2022

Oh, fixed the extension, thank you for this!

https://github.com/vadim-hleif/test-quarkus-mocking/runs/7722415736?check_suite_focus=true

@geoand
Copy link
Contributor

geoand commented Aug 8, 2022

I reopened the issue since this an actual problem, but so far I haven't been able to pinpoint the root cause.

@geoand
Copy link
Contributor

geoand commented Aug 8, 2022

#27179 should take care of the problem

geoand added a commit to geoand/quarkus that referenced this issue Aug 8, 2022
geoand added a commit to geoand/quarkus that referenced this issue Aug 8, 2022
@quarkus-bot quarkus-bot bot added this to the 2.12 - main milestone Aug 8, 2022
geoand added a commit that referenced this issue Aug 8, 2022
Ensure that Quarkus can mock final methods of beans created by producers
@gsmet gsmet modified the milestones: 2.12.0.CR1, 2.11.3.Final Aug 23, 2022
gsmet pushed a commit to gsmet/quarkus that referenced this issue Aug 23, 2022
miador pushed a commit to miador/quarkus that referenced this issue Sep 6, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/testing kind/bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants