From dfea3d05aa4abd0493a2714c0a77edd13a4a2f18 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 8 Sep 2023 19:25:31 +0200 Subject: [PATCH] Revise support for JSR-330 and Jakarta @Inject for autowiring test constructors Closes gh-29851 --- .../integration-junit-jupiter.adoc | 5 +- spring-test/spring-test.gradle | 2 +- .../test/context/TestConstructor.java | 22 +++-- .../context/support/TestConstructorUtils.java | 62 ++++++++----- .../InjectAnnotationIntegrationTests.java | 56 ------------ ...kartaInjectAnnotationIntegrationTests.java | 35 ------- ...JavaxInjectAnnotationIntegrationTests.java | 35 ------- ...terAutowiredConstructorInjectionTests.java | 91 ++++++++++++------- 8 files changed, 117 insertions(+), 191 deletions(-) delete mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/InjectAnnotationIntegrationTests.java delete mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/JakartaInjectAnnotationIntegrationTests.java delete mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/JavaxInjectAnnotationIntegrationTests.java diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit-jupiter.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit-jupiter.adoc index 2876f194df..b035e01a1d 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit-jupiter.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit-jupiter.adoc @@ -170,8 +170,9 @@ of a test class constructor are autowired from components in the test's If `@TestConstructor` is not present or meta-present on a test class, the default _test constructor autowire mode_ will be used. See the tip below for details on how to change -the default mode. Note, however, that a local declaration of `@Autowired` on a -constructor takes precedence over both `@TestConstructor` and the default mode. +the default mode. Note, however, that a local declaration of `@Autowired`, +`@jakarta.inject.Inject`, or `@javax.inject.Inject` on a constructor takes precedence +over both `@TestConstructor` and the default mode. .Changing the default test constructor autowire mode [TIP] diff --git a/spring-test/spring-test.gradle b/spring-test/spring-test.gradle index 8e57b80ed9..ed316f9f77 100644 --- a/spring-test/spring-test.gradle +++ b/spring-test/spring-test.gradle @@ -27,6 +27,7 @@ dependencies { optional("jakarta.websocket:jakarta.websocket-api") optional("jakarta.websocket:jakarta.websocket-client-api") optional("jakarta.xml.bind:jakarta.xml.bind-api") + optional("javax.inject:javax.inject") optional("junit:junit") optional("net.sourceforge.htmlunit:htmlunit") { exclude group: "commons-logging", module: "commons-logging" @@ -70,7 +71,6 @@ dependencies { testImplementation("jakarta.mail:jakarta.mail-api") testImplementation("jakarta.validation:jakarta.validation-api") testImplementation("javax.cache:cache-api") - testImplementation("javax.inject:javax.inject:1") testImplementation("org.apache.httpcomponents:httpclient") { exclude group: "commons-logging", module: "commons-logging" } diff --git a/spring-test/src/main/java/org/springframework/test/context/TestConstructor.java b/spring-test/src/main/java/org/springframework/test/context/TestConstructor.java index a274745628..1f286429e9 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestConstructor.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestConstructor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,9 +38,10 @@ import org.springframework.lang.Nullable; * on a test class, the default test constructor autowire mode will be * used. See {@link #TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME} for details on * how to change the default mode. Note, however, that a local declaration of - * {@link org.springframework.beans.factory.annotation.Autowired @Autowired} on - * a constructor takes precedence over both {@code @TestConstructor} and the default - * mode. + * {@link org.springframework.beans.factory.annotation.Autowired @Autowired} + * {@link jakarta.inject.Inject @jakarta.inject.Inject}, or + * {@link javax.inject.Inject @javax.inject.Inject} on a constructor takes + * precedence over both {@code @TestConstructor} and the default mode. * *

This annotation may be used as a meta-annotation to create custom * composed annotations. @@ -60,6 +61,8 @@ import org.springframework.lang.Nullable; * @author Sam Brannen * @since 5.2 * @see org.springframework.beans.factory.annotation.Autowired @Autowired + * @see jakarta.inject.Inject @jakarta.inject.Inject + * @see javax.inject.Inject @javax.inject.Inject * @see org.springframework.test.context.junit.jupiter.SpringExtension SpringExtension * @see org.springframework.test.context.junit.jupiter.SpringJUnitConfig @SpringJUnitConfig * @see org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig @SpringJUnitWebConfig @@ -104,6 +107,8 @@ public @interface TestConstructor { * @return an {@link AutowireMode} to take precedence over the global default * @see #TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME * @see org.springframework.beans.factory.annotation.Autowired @Autowired + * @see jakarta.inject.Inject @jakarta.inject.Inject + * @see javax.inject.Inject @javax.inject.Inject * @see AutowireMode#ALL * @see AutowireMode#ANNOTATED */ @@ -120,7 +125,9 @@ public @interface TestConstructor { /** * All test constructor parameters will be autowired as if the constructor * itself were annotated with - * {@link org.springframework.beans.factory.annotation.Autowired @Autowired}. + * {@link org.springframework.beans.factory.annotation.Autowired @Autowired}, + * {@link jakarta.inject.Inject @jakarta.inject.Inject}, or + * {@link javax.inject.Inject @javax.inject.Inject}. * @see #ANNOTATED */ ALL, @@ -131,7 +138,10 @@ public @interface TestConstructor { * {@link org.springframework.beans.factory.annotation.Autowired @Autowired}, * {@link org.springframework.beans.factory.annotation.Qualifier @Qualifier}, * or {@link org.springframework.beans.factory.annotation.Value @Value}, - * or if the constructor itself is annotated with {@code @Autowired}. + * or if the constructor itself is annotated with + * {@link org.springframework.beans.factory.annotation.Autowired @Autowired}, + * {@link jakarta.inject.Inject @jakarta.inject.Inject}, or + * {@link javax.inject.Inject @javax.inject.Inject}. * @see #ALL */ ANNOTATED; diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java index 981f0d9688..017d14318b 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,9 @@ import java.lang.reflect.Executable; import java.util.LinkedHashSet; import java.util.Set; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.SpringProperties; import org.springframework.core.annotation.AnnotatedElementUtils; @@ -41,8 +44,37 @@ import org.springframework.util.ClassUtils; * @since 5.2 * @see TestConstructor */ +@SuppressWarnings("unchecked") public abstract class TestConstructorUtils { + private static final Log logger = LogFactory.getLog(TestConstructorUtils.class); + + private static final Set> autowiredAnnotationTypes = new LinkedHashSet<>(2); + + static { + autowiredAnnotationTypes.add(Autowired.class); + + ClassLoader classLoader = TestConstructorUtils.class.getClassLoader(); + try { + autowiredAnnotationTypes.add((Class) + ClassUtils.forName("jakarta.inject.Inject", classLoader)); + logger.trace("'jakarta.inject.Inject' annotation found and supported for autowiring"); + } + catch (ClassNotFoundException ex) { + // jakarta.inject API not available - simply skip. + } + + try { + autowiredAnnotationTypes.add((Class) + ClassUtils.forName("javax.inject.Inject", classLoader)); + logger.trace("'javax.inject.Inject' annotation found and supported for autowiring"); + } + catch (ClassNotFoundException ex) { + // javax.inject API not available - simply skip. + } + } + + private TestConstructorUtils() { } @@ -103,8 +135,9 @@ public abstract class TestConstructorUtils { * conditions is {@code true}. * *

    - *
  1. The constructor is annotated with {@link Autowired @Autowired}.
  2. - *
  3. The constructor is annotated with {@link jakarta.inject.Inject} or {@code javax.inject.Inject}.
  4. + *
  5. The constructor is annotated with {@link Autowired @Autowired}, + * {@link jakarta.inject.Inject @jakarta.inject.Inject}, or + * {@link javax.inject.Inject @javax.inject.Inject}.
  6. *
  7. {@link TestConstructor @TestConstructor} is present or * meta-present on the test class with * {@link TestConstructor#autowireMode() autowireMode} set to @@ -152,30 +185,9 @@ public abstract class TestConstructorUtils { return (autowireMode == AutowireMode.ALL); } - @SuppressWarnings("unchecked") private static boolean isAnnotatedWithAutowiredOrInject(Constructor constructor) { - Set> autowiredAnnotationTypes = new LinkedHashSet<>(); - - autowiredAnnotationTypes.add(Autowired.class); - - try { - autowiredAnnotationTypes.add((Class) - ClassUtils.forName("jakarta.inject.Inject", TestConstructorUtils.class.getClassLoader())); - } - catch (ClassNotFoundException ex) { - // jakarta.inject API not available - simply skip. - } - - try { - autowiredAnnotationTypes.add((Class) - ClassUtils.forName("javax.inject.Inject", TestConstructorUtils.class.getClassLoader())); - } - catch (ClassNotFoundException ex) { - // javax.inject API not available - simply skip. - } - return autowiredAnnotationTypes.stream() - .anyMatch(autowiredAnnotationType -> AnnotatedElementUtils.hasAnnotation(constructor, autowiredAnnotationType)); + .anyMatch(annotationType -> AnnotatedElementUtils.hasAnnotation(constructor, annotationType)); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/InjectAnnotationIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/InjectAnnotationIntegrationTests.java deleted file mode 100644 index 8e71198be6..0000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/InjectAnnotationIntegrationTests.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test.context.junit.jupiter; - -import org.junit.jupiter.api.Test; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Common test implementation for integration tests in order to verify support - * for {@link jakarta.inject.Inject} and {@link javax.inject.Inject}. - * - * @author Florian Lehmann - * @since 6.0.5 - */ -@SpringJUnitConfig -public abstract class InjectAnnotationIntegrationTests { - - private final String foo; - - public InjectAnnotationIntegrationTests(String foo) { - this.foo = foo; - } - - @Test - public void beanInjected() { - assertThat(this.foo).isEqualTo("foo"); - } - - @Configuration - static class Config { - - @Bean - String foo() { - return "foo"; - } - - } -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/JakartaInjectAnnotationIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/JakartaInjectAnnotationIntegrationTests.java deleted file mode 100644 index 048cfa50ad..0000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/JakartaInjectAnnotationIntegrationTests.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test.context.junit.jupiter; - -import jakarta.inject.Inject; - -/** - * Integration tests which verify support for {@link jakarta.inject.Inject}. - * - * @author Florian Lehmann - * @since 6.0.5 - */ -@SpringJUnitConfig -public class JakartaInjectAnnotationIntegrationTests extends InjectAnnotationIntegrationTests { - - @Inject - public JakartaInjectAnnotationIntegrationTests(String foo) { - super(foo); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/JavaxInjectAnnotationIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/JavaxInjectAnnotationIntegrationTests.java deleted file mode 100644 index 1f6432eecd..0000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/JavaxInjectAnnotationIntegrationTests.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test.context.junit.jupiter; - -import javax.inject.Inject; - -/** - * Integration tests which verify support for {@link javax.inject.Inject}. - * - * @author Florian Lehmann - * @since 6.0.5 - */ -@SpringJUnitConfig -public class JavaxInjectAnnotationIntegrationTests extends InjectAnnotationIntegrationTests { - - @Inject - public JavaxInjectAnnotationIntegrationTests(String foo) { - super(foo); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterAutowiredConstructorInjectionTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterAutowiredConstructorInjectionTests.java index 78ba6cbe39..923cc24c4b 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterAutowiredConstructorInjectionTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterAutowiredConstructorInjectionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.test.context.junit.jupiter; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -28,52 +29,80 @@ import org.springframework.test.context.junit.jupiter.comics.Person; import static org.assertj.core.api.Assertions.assertThat; /** - * Integration tests which demonstrate support for {@link Autowired @Autowired} - * test class constructors with the Spring TestContext Framework and JUnit Jupiter. + * Integration tests which demonstrate support for autowired test class + * constructors with the Spring TestContext Framework and JUnit Jupiter. * * @author Sam Brannen * @since 5.0 * @see SpringExtension * @see SpringJUnitJupiterConstructorInjectionTests */ -@SpringJUnitConfig(TestConfig.class) -@TestPropertySource(properties = "enigma = 42") class SpringJUnitJupiterAutowiredConstructorInjectionTests { - final ApplicationContext applicationContext; - final Person dilbert; - final Dog dog; - final Integer enigma; + @Nested + class SpringAutowiredTests extends BaseClass { - @Autowired - SpringJUnitJupiterAutowiredConstructorInjectionTests(ApplicationContext applicationContext, Person dilbert, Dog dog, - @Value("${enigma}") Integer enigma) { - - this.applicationContext = applicationContext; - this.dilbert = dilbert; - this.dog = dog; - this.enigma = enigma; + @Autowired + SpringAutowiredTests(ApplicationContext context, Person dilbert, Dog dog, @Value("${enigma}") Integer enigma) { + super(context, dilbert, dog, enigma); + } } - @Test - void applicationContextInjected() { - assertThat(applicationContext).as("ApplicationContext should have been injected by Spring").isNotNull(); - assertThat(applicationContext.getBean("dilbert", Person.class)).isEqualTo(this.dilbert); + @Nested + class JakartaInjectTests extends BaseClass { + + @jakarta.inject.Inject + JakartaInjectTests(ApplicationContext context, Person dilbert, Dog dog, @Value("${enigma}") Integer enigma) { + super(context, dilbert, dog, enigma); + } } - @Test - void beansInjected() { - assertThat(this.dilbert).as("Dilbert should have been @Autowired by Spring").isNotNull(); - assertThat(this.dilbert.getName()).as("Person's name").isEqualTo("Dilbert"); + @Nested + class JavaxInjectTests extends BaseClass { - assertThat(this.dog).as("Dogbert should have been @Autowired by Spring").isNotNull(); - assertThat(this.dog.getName()).as("Dog's name").isEqualTo("Dogbert"); + @javax.inject.Inject + JavaxInjectTests(ApplicationContext context, Person dilbert, Dog dog, @Value("${enigma}") Integer enigma) { + super(context, dilbert, dog, enigma); + } } - @Test - void propertyPlaceholderInjected() { - assertThat(this.enigma).as("Enigma should have been injected via @Value by Spring").isNotNull(); - assertThat(this.enigma).as("enigma").isEqualTo(42); + + @SpringJUnitConfig(TestConfig.class) + @TestPropertySource(properties = "enigma = 42") + private static abstract class BaseClass { + + final ApplicationContext context; + final Person dilbert; + final Dog dog; + final Integer enigma; + + BaseClass(ApplicationContext context, Person dilbert, Dog dog, @Value("${enigma}") Integer enigma) { + this.context = context; + this.dilbert = dilbert; + this.dog = dog; + this.enigma = enigma; + } + + @Test + void applicationContextInjected() { + assertThat(context).as("ApplicationContext should have been injected").isNotNull(); + assertThat(context.getBean("dilbert", Person.class)).isEqualTo(this.dilbert); + } + + @Test + void beansInjected() { + assertThat(this.dilbert).as("Dilbert should have been injected").isNotNull(); + assertThat(this.dilbert.getName()).as("Person's name").isEqualTo("Dilbert"); + + assertThat(this.dog).as("Dogbert should have been injected").isNotNull(); + assertThat(this.dog.getName()).as("Dog's name").isEqualTo("Dogbert"); + } + + @Test + void propertyPlaceholderInjected() { + assertThat(this.enigma).as("Enigma should have been injected via @Value").isEqualTo(42); + } + } }