Repository: micronaut-projects/micronaut-test
Pull request #776 (2023-06-01 08:01:13)

Merge Up 3.9.x into master

sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=micronaut-projects_micronaut-test&pullRequest=776)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=CODE_SMELL)

[![43.8%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/40-16px.png '43.8%')](https://sonarcloud.io/component_measures?id=micronaut-projects_micronaut-test&pullRequest=776&metric=new_coverage&view=list) [43.8% Coverage](https://sonarcloud.io/component_measures?id=micronaut-projects_micronaut-test&pullRequest=776&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=micronaut-projects_micronaut-test&pullRequest=776&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=micronaut-projects_micronaut-test&pullRequest=776&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=micronaut-projects_micronaut-test&pullRequest=776)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-test&pullRequest=776&resolved=false&types=CODE_SMELL)

[![43.8%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/40-16px.png '43.8%')](https://sonarcloud.io/component_measures?id=micronaut-projects_micronaut-test&pullRequest=776&metric=new_coverage&view=list) [43.8% Coverage](https://sonarcloud.io/component_measures?id=micronaut-projects_micronaut-test&pullRequest=776&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=micronaut-projects_micronaut-test&pullRequest=776&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=micronaut-projects_micronaut-test&pullRequest=776&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/src/main/docs/guide/introduction.adoc b/src/main/docs/guide/introduction.adoc
index b8978766..1c0669e1 100644
--- a/src/main/docs/guide/introduction.adoc
+++ b/src/main/docs/guide/introduction.adoc
@@ -32,13 +32,31 @@ This is achieved through a set of annotations:
 
 These annotations use internal Micronaut features and do not mock any part of Micronaut itself. When you run a test within `@MicronautTest` it is running your real application.
 
-In some tests you may need a reference to the `ApplicationContext` and/or the `EmbeddedServer` (for example, to create an instance of an `HttpClient`). Rather than defining these as properties of the test (such as a `@Shared` property in Spock), when using `@MicronautTest` you can reference the server/context that was started up for you, and inject them directly in your test.  
+In some tests you may need a reference to the `ApplicationContext` and/or the `EmbeddedServer` (for example, to create an instance of an `HttpClient`). Rather than defining these as properties of the test (such as a `@Shared` property in Spock), when using `@MicronautTest` you can reference the server/context that was started up for you, and inject them directly in your test.
 
 [source,groovy]
 ----
-@Inject 
+@Inject
 EmbeddedServer server //refers to the server that was started up for this test suite
 
-@Inject 
+@Inject
 ApplicationContext context //refers to the current application context within the scope of the test
+----
+
+### Eager Singleton Initialization
+
+If you enable https://docs.micronaut.io/latest/guide/index.html#eagerInit[eager singleton initialization] in your application, the Micronaut Framework eagerly initializes all singletons at startup time. This can be useful for applications that need to perform some initialization at startup time, such as registering a bean with a third party library.
+
+However, as tests annotated with `@MicronautTest` are implicitly in the `Singleton` scope, this can cause problems injecting some beans (for example an `HttpClient`) into your test class.
+
+To avoid this, you can either disable eager singleton initialization for your tests, or you will need to manually get an instance of the bean you would normally inject.  As an example, to get an `HttpClient` you could do:
+
+[source,java]
+.Using an HttpClient in a test with eager singleton initialization enabled
+----
+include::test-junit5/src/test/java/io/micronaut/test/junit5/EagerInitializationTest.java[tags=eager, indent=0]
+----
 
+<1> Inject the `EmbeddedServer` as normal
+<2> Create a `Supplier` that will create the `HttpClient` when it is first called
+<3> Use the `Supplier` to get the `HttpClient` and make the request
diff --git a/src/main/docs/guide/junit5.adoc b/src/main/docs/guide/junit5.adoc
index d55c9386..7a4991fa 100644
--- a/src/main/docs/guide/junit5.adoc
+++ b/src/main/docs/guide/junit5.adoc
@@ -9,8 +9,9 @@ dependencies {
     testAnnotationProcessor "io.micronaut:micronaut-inject-java"
     ...
     testImplementation("org.junit.jupiter:junit-jupiter-api")
-    testImplementation("io.micronaut.test:micronaut-test-junit5:{version}")
-    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:{junitVersion}")
+    testImplementation("io.micronaut.test:micronaut-test-junit5")
+    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
+    testImplementation("org.junit.jupiter:junit-jupiter-engine")
 }
 
 // use JUnit 5 platform


Test patch:
diff --git a/test-core/src/main/java/io/micronaut/test/annotation/MicronautTestValue.java b/test-core/src/main/java/io/micronaut/test/annotation/MicronautTestValue.java
index da761c8d..cb0f8e63 100644
--- a/test-core/src/main/java/io/micronaut/test/annotation/MicronautTestValue.java
+++ b/test-core/src/main/java/io/micronaut/test/annotation/MicronautTestValue.java
@@ -39,6 +39,8 @@ public class MicronautTestValue {
     private final TransactionMode transactionMode;
     private final boolean startApplication;
 
+    private final boolean resolveParameters;
+
     /**
      * Default constructor.
      *
@@ -52,11 +54,14 @@ public class MicronautTestValue {
      * @param contextBuilder  The builder
      * @param transactionMode The transaction mode
      * @param startApplication Whether the start the app
+     * @param resolveParameters Whether to resolve parameters to beans
      */
     @Creator
     public MicronautTestValue(Class<?> application, String[] environments, String[] packages, String[] propertySources,
                               boolean rollback, boolean transactional, boolean rebuildContext,
-                              Class<? extends ApplicationContextBuilder>[] contextBuilder, TransactionMode transactionMode, boolean startApplication) {
+                              Class<? extends ApplicationContextBuilder>[] contextBuilder, TransactionMode transactionMode,
+                              boolean startApplication,
+                              boolean resolveParameters) {
         this.application = application;
         this.environments = environments;
         this.packages = packages;
@@ -67,6 +72,7 @@ public class MicronautTestValue {
         this.contextBuilder = contextBuilder;
         this.transactionMode = transactionMode;
         this.startApplication = startApplication;
+        this.resolveParameters = resolveParameters;
     }
 
     /**
@@ -150,4 +156,11 @@ public class MicronautTestValue {
     public boolean startApplication() {
         return startApplication;
     }
+
+    /**
+     * @return Whether to resolve test method parameters.
+     */
+    public boolean isResolveParameters() {
+        return resolveParameters;
+    }
 }
diff --git a/test-core/src/main/java/io/micronaut/test/extensions/AbstractMicronautExtension.java b/test-core/src/main/java/io/micronaut/test/extensions/AbstractMicronautExtension.java
index 79e31fca..853e22da 100644
--- a/test-core/src/main/java/io/micronaut/test/extensions/AbstractMicronautExtension.java
+++ b/test-core/src/main/java/io/micronaut/test/extensions/AbstractMicronautExtension.java
@@ -85,7 +85,7 @@ public abstract class AbstractMicronautExtension<C> implements TestExecutionList
     protected Map<String, Object> testProperties = new LinkedHashMap<>();
     protected Map<String, Object> oldValues = new LinkedHashMap<>();
 
-    private MicronautTestValue testAnnotationValue;
+    protected MicronautTestValue testAnnotationValue;
     private ApplicationContextBuilder builder = ApplicationContext.builder();
     private List<TestExecutionListener> listeners;
     private List<TestMethodInterceptor<Object>> interceptors;
diff --git a/test-junit5/src/main/java/io/micronaut/test/extensions/junit5/MicronautJunit5Extension.java b/test-junit5/src/main/java/io/micronaut/test/extensions/junit5/MicronautJunit5Extension.java
index 2b358958..17ffe866 100644
--- a/test-junit5/src/main/java/io/micronaut/test/extensions/junit5/MicronautJunit5Extension.java
+++ b/test-junit5/src/main/java/io/micronaut/test/extensions/junit5/MicronautJunit5Extension.java
@@ -335,15 +335,23 @@ public class MicronautJunit5Extension extends AbstractMicronautExtension<Extensi
 
     @Override
     public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
-        final Argument<?> argument = getArgument(parameterContext, applicationContext);
-        if (argument != null) {
-            if (argument.isAnnotationPresent(Value.class) || argument.isAnnotationPresent(Property.class)) {
-                return true;
+        if (this.testAnnotationValue != null) {
+            if (!this.testAnnotationValue.isResolveParameters()) {
+                return false;
+            }
+
+            final Argument<?> argument = getArgument(parameterContext, applicationContext);
+            if (argument != null) {
+                if (argument.isAnnotationPresent(Value.class) || argument.isAnnotationPresent(Property.class)) {
+                    return true;
+                } else {
+                    return applicationContext.containsBean(argument.getType(), resolveQualifier(argument));
+                }
             } else {
-                return applicationContext.containsBean(argument.getType(), resolveQualifier(argument));
+                return applicationContext.containsBean(parameterContext.getParameter().getType());
             }
         } else {
-            return applicationContext.containsBean(parameterContext.getParameter().getType());
+            return false;
         }
     }
 
@@ -394,7 +402,8 @@ public class MicronautJunit5Extension extends AbstractMicronautExtension<Extensi
                 micronautTest.rebuildContext(),
                 micronautTest.contextBuilder(),
                 micronautTest.transactionMode(),
-                micronautTest.startApplication());
+                micronautTest.startApplication(),
+                micronautTest.resolveParameters());
     }
 
     private boolean isNestedTestClass(Class<?> testClass) {
diff --git a/test-junit5/src/main/java/io/micronaut/test/extensions/junit5/annotation/MicronautTest.java b/test-junit5/src/main/java/io/micronaut/test/extensions/junit5/annotation/MicronautTest.java
index 816b6cd4..27136dca 100644
--- a/test-junit5/src/main/java/io/micronaut/test/extensions/junit5/annotation/MicronautTest.java
+++ b/test-junit5/src/main/java/io/micronaut/test/extensions/junit5/annotation/MicronautTest.java
@@ -107,4 +107,14 @@ public @interface MicronautTest {
      * @return true if {@link io.micronaut.runtime.EmbeddedApplication} should be started
      */
     boolean startApplication() default true;
+
+    /**
+     * By default, with JUnit 5 the test method parameters will be resolved to beans if possible.
+     * This behaviour can be problematic if in combination with the {@code ParameterizedTest} annotation.
+     * Setting this member to {@code false} will completely disable bean resolution for method parameters.
+     * <p>
+     *
+     * @return Whether to resolve test method parameters as beans.
+     */
+    boolean resolveParameters() default true;
 }
diff --git a/test-junit5/src/test/java/io/micronaut/test/junit5/EagerInitializationTest.java b/test-junit5/src/test/java/io/micronaut/test/junit5/EagerInitializationTest.java
new file mode 100644
index 00000000..1c2e25a0
--- /dev/null
+++ b/test-junit5/src/test/java/io/micronaut/test/junit5/EagerInitializationTest.java
@@ -0,0 +1,51 @@
+package io.micronaut.test.junit5;
+
+import io.micronaut.context.DefaultApplicationContextBuilder;
+import io.micronaut.context.annotation.Property;
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.core.annotation.Introspected;
+import io.micronaut.core.util.SupplierUtil;
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.annotation.Get;
+import io.micronaut.http.client.HttpClient;
+import io.micronaut.runtime.server.EmbeddedServer;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.function.Supplier;
+
+@MicronautTest(contextBuilder = EagerInitializationTest.EagerContextBuilder.class)
+@Property(name = "spec.name", value = "EagerInitializationTest")
+class EagerInitializationTest {
+
+    // tag::eager[]
+    @Inject
+    EmbeddedServer server; // <1>
+
+    Supplier<HttpClient> client = SupplierUtil.memoizedNonEmpty(() ->
+        server.getApplicationContext().createBean(HttpClient.class, server.getURL())); // <2>
+
+    @Test
+    void testEagerSingleton() {
+        Assertions.assertEquals("eager", client.get().toBlocking().retrieve("/eager")); // <3>
+    }
+    // end::eager[]
+
+    @Requires(property = "spec.name", value = "EagerInitializationTest")
+    @Controller("/eager")
+    public static class EagerController {
+        @Get
+        String test() {
+            return "eager";
+        }
+    }
+
+    @Introspected
+    static class EagerContextBuilder extends DefaultApplicationContextBuilder {
+        public EagerContextBuilder() {
+            eagerInitSingletons(true);
+        }
+    }
+}
diff --git a/test-junit5/src/test/java/io/micronaut/test/junit5/FooTest.java b/test-junit5/src/test/java/io/micronaut/test/junit5/FooTest.java
new file mode 100644
index 00000000..e3602e74
--- /dev/null
+++ b/test-junit5/src/test/java/io/micronaut/test/junit5/FooTest.java
@@ -0,0 +1,29 @@
+package io.micronaut.test.junit5;
+
+import java.util.stream.Stream;
+
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Singleton;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+@MicronautTest(resolveParameters = false)
+class FooTest {
+
+    static Stream<Arguments> fooArgs() {
+        return Stream.of(Arguments.of(new Foo()));
+    }
+
+    @ParameterizedTest
+    @MethodSource("fooArgs")
+    void foo(Foo arg) {
+        Assertions.assertNotNull(arg);
+    }
+
+    @Singleton
+    static class Foo {
+    }
+}
+
diff --git a/test-kotest5/src/main/kotlin/io/micronaut/test/extensions/kotest5/MicronautKotest5Extension.kt b/test-kotest5/src/main/kotlin/io/micronaut/test/extensions/kotest5/MicronautKotest5Extension.kt
index 81e9d421..4f51ed60 100644
--- a/test-kotest5/src/main/kotlin/io/micronaut/test/extensions/kotest5/MicronautKotest5Extension.kt
+++ b/test-kotest5/src/main/kotlin/io/micronaut/test/extensions/kotest5/MicronautKotest5Extension.kt
@@ -111,7 +111,8 @@ object MicronautKotest5Extension: TestListener, ConstructorExtension, TestCaseEx
                 micronautTest.rebuildContext,
                 micronautTest.contextBuilder.map { kClass -> kClass.java }.toTypedArray(),
                 micronautTest.transactionMode,
-                micronautTest.startApplication
+                micronautTest.startApplication,
+                false
         )
     }
 
diff --git a/test-spock/src/main/java/io/micronaut/test/extensions/spock/MicronautSpockExtension.java b/test-spock/src/main/java/io/micronaut/test/extensions/spock/MicronautSpockExtension.java
index 030a5bb2..c8915ff6 100644
--- a/test-spock/src/main/java/io/micronaut/test/extensions/spock/MicronautSpockExtension.java
+++ b/test-spock/src/main/java/io/micronaut/test/extensions/spock/MicronautSpockExtension.java
@@ -210,7 +210,8 @@ public class MicronautSpockExtension<T extends Annotation> extends AbstractMicro
                     micronautTest.rebuildContext(),
                     micronautTest.contextBuilder(),
                     micronautTest.transactionMode(),
-                    micronautTest.startApplication());
+                    micronautTest.startApplication(),
+                    false);
         } else {
             return null;
         }