micronaut-projects / micronaut-core

Micronaut Application Framework
http://micronaut.io
Apache License 2.0
6.05k stars 1.06k forks source link

Unexplained io.micronaut.context.exceptions.NonUniqueBeanException in library project component test cases #6601

Open Jim-Harrington opened 2 years ago

Jim-Harrington commented 2 years ago

Expected Behavior

Not sure this is a bug, but I have not been able to explain it. Mainly, I'm hoping for an explanation that I can use to correct the issue.

In my library project I have a single service:

@Singleton @Requires(property = "gts.oci.enabled", value = "true", defaultValue = "true") public class OciAccessClientMicronaut { an annotation that adds the service into consuming application contexts:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(classes = {OciAccessClientMicronaut.class, OciAuthPropertiesMicronaut.class, OciObjectStoragePropertiesMicronaut.class, OciStreamingPropertiesMicronaut.class}) public @interface EnableOciAccessClientMicronaut { }

and two test cases that inject it for use:

` @MicronautTest(environments = {"svcon"}, rebuildContext = true) @EnableOciAccessClientMicronaut @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class OciAccessClientMicronautComponentTest {

@Inject //@Any private OciAccessClientMicronaut undertest; `

` @MicronautTest(environments = {"svcoff"}, rebuildContext = true) //@EnableOciAccessClientMicronaut - works at compile time, so we only want one in suite @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class OciAccessClientMicronautServicesOffComponentTest {

@Inject //@Any private OciAccessClientMicronaut undertest = null; ` Expected Behavior:

As there is only the one registration of the service in the project source, I expect there to be just one bean registration for the class. So, the two test cases should successfully execute without any qualifier applied at the injection site.

Actual Behaviour

Instead, with no qualifier at the injections site in the test cases, I get:

Caused by: io.micronaut.context.exceptions.NonUniqueBeanException: Multiple possible bean candidates found: [com.oracle.gbu.gaes.oci.OciAccessClientMicronaut, com.oracle.gbu.gaes.oci.OciAccessClientMicronaut]

After doing some debugging, I figured out that applying the @Any annotation works around the problem, but I feel like I need to know why it's necessary.

One thing I noticed is that there bean reference classes are generated:

target/classes/com/oracle/gbu/gaes/oci:

$OciAccessClientMicronaut$Definition$Reference.class $OciAccessClientMicronaut$Definition.class

target/test-classes/com/oracle/gbu/gaes/oci:

$OciAccessClientMicronautComponentTest$OciAccessClientMicronaut0$Definition$Reference.class $OciAccessClientMicronautComponentTest$OciAccessClientMicronaut0$Definition.class

and these are the source of the twin bean definitions. I understand the one in "main", but why is the one in "test" being generated? As a test, I deleted the classes in "test" and the @Any annotation was no longer necessary.

Steps To Reproduce

No response

Environment Information

OS: Lunux JDK: 11

Example Application

No response

Version

3.2.0

jameskleeh commented 2 years ago

@Import is for importing beans from other jars. It should not be used for your own classes. Thus having @Singleton on OciAccessClientMicronaut and importing it would cause duplicate bean definitions

Jim-Harrington commented 2 years ago

Thanks for the feedback.

The thing is that the project I'm working in is a library intended to deliver the @Singleton class, so I need an instance of it in the respective test classes. If I don't @Import it, I get a dependency injection error stating that there are no beans of that type to inject. I suspect the reason is that the @Singleton class is in the project "main" source, which is similar to importing beans from a different jar.

graemerocher commented 2 years ago

We will need an example attached

Jim-Harrington commented 2 years ago

OK, here are links to the example service and it's test case:

https://github.com/Jim-Harrington/micronaut-import-question/blob/main/src/main/java/com/example/test/TestService.java https://github.com/Jim-Harrington/micronaut-import-question/blob/main/src/test/java/com/example/test/TestServiceTest.java

The key seems to be the properties that the service depends on. If I don't annotate the test case with an @Import containing the properties class, the case fails with a missing bean definition for that class. If I do annotate the test case with an @Import containing the service and properties classes, I get a duplicate bean definition for the service class unless I @Inject with @Any.