diff --git a/buildSrc/gradle.properties b/buildSrc/gradle.properties index eacaf4f5b871..eced14624837 100644 --- a/buildSrc/gradle.properties +++ b/buildSrc/gradle.properties @@ -1,2 +1,2 @@ org.gradle.caching=true -javaFormatVersion=0.0.42 +javaFormatVersion=0.0.43 diff --git a/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java b/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java index 4216ae6fa21e..b35b3e3b5df6 100644 --- a/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java +++ b/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java @@ -50,7 +50,7 @@ public void apply(Project project) { project.getPlugins().apply(CheckstylePlugin.class); project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g")); CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class); - checkstyle.setToolVersion("10.23.0"); + checkstyle.setToolVersion("10.23.1"); checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle")); String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion(); DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies(); diff --git a/buildSrc/src/main/java/org/springframework/build/TestConventions.java b/buildSrc/src/main/java/org/springframework/build/TestConventions.java index 1283d233765d..bb8f507efac4 100644 --- a/buildSrc/src/main/java/org/springframework/build/TestConventions.java +++ b/buildSrc/src/main/java/org/springframework/build/TestConventions.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -21,6 +21,8 @@ import org.gradle.api.Project; import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.api.tasks.testing.Test; +import org.gradle.api.tasks.testing.TestFrameworkOptions; +import org.gradle.api.tasks.testing.junitplatform.JUnitPlatformOptions; import org.gradle.testretry.TestRetryPlugin; import org.gradle.testretry.TestRetryTaskExtension; @@ -34,6 +36,7 @@ * * @author Brian Clozel * @author Andy Wilkinson + * @author Sam Brannen */ class TestConventions { @@ -50,7 +53,12 @@ private void configureTestConventions(Project project) { } private void configureTests(Project project, Test test) { - test.useJUnitPlatform(); + TestFrameworkOptions existingOptions = test.getOptions(); + test.useJUnitPlatform(options -> { + if (existingOptions instanceof JUnitPlatformOptions junitPlatformOptions) { + options.copyFrom(junitPlatformOptions); + } + }); test.include("**/*Tests.class", "**/*Test.class"); test.setSystemProperties(Map.of( "java.awt.headless", "true", diff --git a/framework-api/framework-api.gradle b/framework-api/framework-api.gradle index df7f3bbd57da..5526a79ba53c 100644 --- a/framework-api/framework-api.gradle +++ b/framework-api/framework-api.gradle @@ -1,6 +1,6 @@ plugins { id 'java-platform' - id 'io.freefair.aggregate-javadoc' version '8.3' + id 'io.freefair.aggregate-javadoc' version '8.13.1' } description = "Spring Framework API Docs" @@ -21,6 +21,7 @@ dependencies { javadoc { title = "${rootProject.description} ${version} API" + failOnError = true options { encoding = "UTF-8" memberLevel = JavadocMemberLevel.PROTECTED diff --git a/framework-docs/modules/ROOT/pages/appendix.adoc b/framework-docs/modules/ROOT/pages/appendix.adoc index 9a8c9048c051..6e7e5cecd0e0 100644 --- a/framework-docs/modules/ROOT/pages/appendix.adoc +++ b/framework-docs/modules/ROOT/pages/appendix.adoc @@ -103,6 +103,14 @@ for details. {spring-framework-api}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`] for details. +| `spring.placeholder.escapeCharacter.default` +| The default escape character for property placeholder support. If not set, `'\'` will +be used. Can be set to a custom escape character or an empty string to disable support +for an escape character. The default escape character be explicitly overridden in +`PropertySourcesPlaceholderConfigurer` and subclasses of `AbstractPropertyResolver`. See +{spring-framework-api}++/core/env/AbstractPropertyResolver.html#DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME++[`AbstractPropertyResolver`] +for details. + | `spring.test.aot.processing.failOnError` | A boolean flag that controls whether errors encountered during AOT processing in the _Spring TestContext Framework_ should result in an exception that fails the overall process. diff --git a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/value-annotations.adoc b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/value-annotations.adoc index 72e70005d0cd..d91aaafb19b4 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/value-annotations.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/value-annotations.adoc @@ -101,8 +101,11 @@ NOTE: When configuring a `PropertySourcesPlaceholderConfigurer` using JavaConfig Using the above configuration ensures Spring initialization failure if any `${}` placeholder could not be resolved. It is also possible to use methods like -`setPlaceholderPrefix`, `setPlaceholderSuffix`, `setValueSeparator`, or -`setEscapeCharacter` to customize placeholders. +`setPlaceholderPrefix()`, `setPlaceholderSuffix()`, `setValueSeparator()`, or +`setEscapeCharacter()` to customize the placeholder syntax. In addition, the default +escape character can be changed or disabled globally by setting the +`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via +the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism). NOTE: Spring Boot configures by default a `PropertySourcesPlaceholderConfigurer` bean that will get properties from `application.properties` and `application.yml` files. diff --git a/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc b/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc index bd4f6da550e1..56641fd847eb 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc @@ -314,7 +314,7 @@ Thus, marking it for lazy initialization will be ignored, and the [[beans-factory-placeholderconfigurer]] -=== Example: The Class Name Substitution `PropertySourcesPlaceholderConfigurer` +=== Example: Property Placeholder Substitution with `PropertySourcesPlaceholderConfigurer` You can use the `PropertySourcesPlaceholderConfigurer` to externalize property values from a bean definition in a separate file by using the standard Java `Properties` format. @@ -341,8 +341,8 @@ with placeholder values is defined: The example shows properties configured from an external `Properties` file. At runtime, a `PropertySourcesPlaceholderConfigurer` is applied to the metadata that replaces some -properties of the DataSource. The values to replace are specified as placeholders of the -form pass:q[`${property-name}`], which follows the Ant and log4j and JSP EL style. +properties of the `DataSource`. The values to replace are specified as placeholders of the +form pass:q[`${property-name}`], which follows the Ant, log4j, and JSP EL style. The actual values come from another file in the standard Java `Properties` format: @@ -355,11 +355,15 @@ jdbc.password=root ---- Therefore, the `${jdbc.username}` string is replaced at runtime with the value, 'sa', and -the same applies for other placeholder values that match keys in the properties file. -The `PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and -attributes of a bean definition. Furthermore, you can customize the placeholder prefix and suffix. - -With the `context` namespace introduced in Spring 2.5, you can configure property placeholders +the same applies for other placeholder values that match keys in the properties file. The +`PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and +attributes of a bean definition. Furthermore, you can customize the placeholder prefix, +suffix, default value separator, and escape character. In addition, the default escape +character can be changed or disabled globally by setting the +`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via +the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism). + +With the `context` namespace, you can configure property placeholders with a dedicated configuration element. You can provide one or more locations as a comma-separated list in the `location` attribute, as the following example shows: diff --git a/framework-docs/modules/ROOT/pages/languages/kotlin/spring-projects-in.adoc b/framework-docs/modules/ROOT/pages/languages/kotlin/spring-projects-in.adoc index 3fa561bf51c5..2a8670de0740 100644 --- a/framework-docs/modules/ROOT/pages/languages/kotlin/spring-projects-in.adoc +++ b/framework-docs/modules/ROOT/pages/languages/kotlin/spring-projects-in.adoc @@ -190,7 +190,7 @@ NOTE: If you use Spring Boot, you should probably use instead of `@Value` annotations. As an alternative, you can customize the property placeholder prefix by declaring the -following configuration beans: +following `PropertySourcesPlaceholderConfigurer` bean: [source,kotlin,indent=0] ---- @@ -200,8 +200,10 @@ following configuration beans: } ---- -You can customize existing code (such as Spring Boot actuators or `@LocalServerPort`) -that uses the `${...}` syntax, with configuration beans, as the following example shows: +You can support components (such as Spring Boot actuators or `@LocalServerPort`) that use +the standard `${...}` syntax alongside components that use the custom `%{...}` syntax by +declaring multiple `PropertySourcesPlaceholderConfigurer` beans, as the following example +shows: [source,kotlin,indent=0] ---- @@ -215,6 +217,9 @@ that uses the `${...}` syntax, with configuration beans, as the following exampl fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer() ---- +In addition, the default escape character can be changed or disabled globally by setting +the `spring.placeholder.escapeCharacter.default` property via a JVM system property (or +via the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism). [[checked-exceptions]] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit4.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit4.adoc index 17fdf5c9bef0..fdc6470f9682 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit4.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit4.adoc @@ -2,8 +2,9 @@ = Spring JUnit 4 Testing Annotations The following annotations are supported only when used in conjunction with the -xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-runner[SpringRunner], xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit 4 rules] -, or xref:testing/testcontext-framework/support-classes.adoc#testcontext-support-classes-junit4[Spring's JUnit 4 support classes]: +xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-runner[SpringRunner], +xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit 4 rules], or +xref:testing/testcontext-framework/support-classes.adoc#testcontext-support-classes-junit4[Spring's JUnit 4 support classes]: * xref:testing/annotations/integration-junit4.adoc#integration-testing-annotations-junit4-ifprofilevalue[`@IfProfileValue`] * xref:testing/annotations/integration-junit4.adoc#integration-testing-annotations-junit4-profilevaluesourceconfiguration[`@ProfileValueSourceConfiguration`] diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/support-classes.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/support-classes.adoc index 1ee5856ecfd0..a77980058736 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/support-classes.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/support-classes.adoc @@ -1,166 +1,9 @@ [[testcontext-support-classes]] = TestContext Framework Support Classes -This section describes the various classes that support the Spring TestContext Framework. +This section describes the various classes that support the Spring TestContext Framework +in JUnit and TestNG. -[[testcontext-junit4-runner]] -== Spring JUnit 4 Runner - -The Spring TestContext Framework offers full integration with JUnit 4 through a custom -runner (supported on JUnit 4.12 or higher). By annotating test classes with -`@RunWith(SpringJUnit4ClassRunner.class)` or the shorter `@RunWith(SpringRunner.class)` -variant, developers can implement standard JUnit 4-based unit and integration tests and -simultaneously reap the benefits of the TestContext framework, such as support for -loading application contexts, dependency injection of test instances, transactional test -method execution, and so on. If you want to use the Spring TestContext Framework with an -alternative runner (such as JUnit 4's `Parameterized` runner) or third-party runners -(such as the `MockitoJUnitRunner`), you can, optionally, use -xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's support for JUnit rules] instead. - -The following code listing shows the minimal requirements for configuring a test class to -run with the custom Spring `Runner`: - -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes"] ----- - @RunWith(SpringRunner.class) - @TestExecutionListeners({}) - public class SimpleTest { - - @Test - public void testMethod() { - // test logic... - } - } ----- - -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes"] ----- - @RunWith(SpringRunner::class) - @TestExecutionListeners - class SimpleTest { - - @Test - fun testMethod() { - // test logic... - } - } ----- -====== - -In the preceding example, `@TestExecutionListeners` is configured with an empty list, to -disable the default listeners, which otherwise would require an `ApplicationContext` to -be configured through `@ContextConfiguration`. - -[[testcontext-junit4-rules]] -== Spring JUnit 4 Rules - -The `org.springframework.test.context.junit4.rules` package provides the following JUnit -4 rules (supported on JUnit 4.12 or higher): - -* `SpringClassRule` -* `SpringMethodRule` - -`SpringClassRule` is a JUnit `TestRule` that supports class-level features of the Spring -TestContext Framework, whereas `SpringMethodRule` is a JUnit `MethodRule` that supports -instance-level and method-level features of the Spring TestContext Framework. - -In contrast to the `SpringRunner`, Spring's rule-based JUnit support has the advantage of -being independent of any `org.junit.runner.Runner` implementation and can, therefore, be -combined with existing alternative runners (such as JUnit 4's `Parameterized`) or -third-party runners (such as the `MockitoJUnitRunner`). - -To support the full functionality of the TestContext framework, you must combine a -`SpringClassRule` with a `SpringMethodRule`. The following example shows the proper way -to declare these rules in an integration test: - -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes"] ----- - // Optionally specify a non-Spring Runner via @RunWith(...) - @ContextConfiguration - public class IntegrationTest { - - @ClassRule - public static final SpringClassRule springClassRule = new SpringClassRule(); - - @Rule - public final SpringMethodRule springMethodRule = new SpringMethodRule(); - - @Test - public void testMethod() { - // test logic... - } - } ----- - -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes"] ----- - // Optionally specify a non-Spring Runner via @RunWith(...) - @ContextConfiguration - class IntegrationTest { - - @Rule - val springMethodRule = SpringMethodRule() - - @Test - fun testMethod() { - // test logic... - } - - companion object { - @ClassRule - val springClassRule = SpringClassRule() - } - } ----- -====== - -[[testcontext-support-classes-junit4]] -== JUnit 4 Support Classes - -The `org.springframework.test.context.junit4` package provides the following support -classes for JUnit 4-based test cases (supported on JUnit 4.12 or higher): - -* `AbstractJUnit4SpringContextTests` -* `AbstractTransactionalJUnit4SpringContextTests` - -`AbstractJUnit4SpringContextTests` is an abstract base test class that integrates the -Spring TestContext Framework with explicit `ApplicationContext` testing support in a -JUnit 4 environment. When you extend `AbstractJUnit4SpringContextTests`, you can access a -`protected` `applicationContext` instance variable that you can use to perform explicit -bean lookups or to test the state of the context as a whole. - -`AbstractTransactionalJUnit4SpringContextTests` is an abstract transactional extension of -`AbstractJUnit4SpringContextTests` that adds some convenience functionality for JDBC -access. This class expects a `javax.sql.DataSource` bean and a -`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you -extend `AbstractTransactionalJUnit4SpringContextTests`, you can access a `protected` -`jdbcTemplate` instance variable that you can use to run SQL statements to query the -database. You can use such queries to confirm database state both before and after -running database-related application code, and Spring ensures that such queries run in -the scope of the same transaction as the application code. When used in conjunction with -an ORM tool, be sure to avoid xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives]. -As mentioned in xref:testing/support-jdbc.adoc[JDBC Testing Support], -`AbstractTransactionalJUnit4SpringContextTests` also provides convenience methods that -delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`. -Furthermore, `AbstractTransactionalJUnit4SpringContextTests` provides an -`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`. - -TIP: These classes are a convenience for extension. If you do not want your test classes -to be tied to a Spring-specific class hierarchy, you can configure your own custom test -classes by using `@RunWith(SpringRunner.class)` or xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit rules] -. [[testcontext-junit-jupiter-extension]] == SpringExtension for JUnit Jupiter @@ -177,14 +20,17 @@ following features above and beyond the feature set that Spring supports for JUn TestNG: * Dependency injection for test constructors, test methods, and test lifecycle callback - methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency Injection with the `SpringExtension`] for further details. + methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency + Injection with the `SpringExtension`] for further details. * Powerful support for link:https://junit.org/junit5/docs/current/user-guide/#extensions-conditions[conditional test execution] based on SpEL expressions, environment variables, system properties, and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in - xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations] for further details and examples. + xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations] + for further details and examples. * Custom composed annotations that combine annotations from Spring and JUnit Jupiter. See the `@TransactionalDevTestConfig` and `@TransactionalIntegrationTest` examples in - xref:testing/annotations/integration-meta.adoc[Meta-Annotation Support for Testing] for further details. + xref:testing/annotations/integration-meta.adoc[Meta-Annotation Support for Testing] for + further details. The following code listing shows how to configure a test class to use the `SpringExtension` in conjunction with `@ContextConfiguration`: @@ -307,7 +153,8 @@ Kotlin:: ====== See the documentation for `@SpringJUnitConfig` and `@SpringJUnitWebConfig` in -xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations] for further details. +xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations] +for further details. [[testcontext-junit-jupiter-di]] === Dependency Injection with the `SpringExtension` @@ -318,10 +165,9 @@ extension API from JUnit Jupiter, which lets Spring provide dependency injection constructors, test methods, and test lifecycle callback methods. Specifically, the `SpringExtension` can inject dependencies from the test's -`ApplicationContext` into test constructors and methods that are annotated with -Spring's `@BeforeTransaction` and `@AfterTransaction` or JUnit's `@BeforeAll`, -`@AfterAll`, `@BeforeEach`, `@AfterEach`, `@Test`, `@RepeatedTest`, `@ParameterizedTest`, -and others. +`ApplicationContext` into test constructors and methods that are annotated with Spring's +`@BeforeTransaction` and `@AfterTransaction` or JUnit's `@BeforeAll`, `@AfterAll`, +`@BeforeEach`, `@AfterEach`, `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and others. [[testcontext-junit-jupiter-di-constructor]] @@ -341,8 +187,9 @@ autowirable if one of the following conditions is met (in order of precedence). attribute set to `ALL`. * The default _test constructor autowire mode_ has been changed to `ALL`. -See xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`] for details on the use of -`@TestConstructor` and how to change the global _test constructor autowire mode_. +See xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`] +for details on the use of `@TestConstructor` and how to change the global _test +constructor autowire mode_. WARNING: If the constructor for a test class is considered to be _autowirable_, Spring assumes the responsibility for resolving arguments for all parameters in the constructor. @@ -407,8 +254,9 @@ Kotlin:: Note that this feature lets test dependencies be `final` and therefore immutable. If the `spring.test.constructor.autowire.mode` property is to `all` (see -xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]), we can omit the declaration of -`@Autowired` on the constructor in the previous example, resulting in the following. +xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]), +we can omit the declaration of `@Autowired` on the constructor in the previous example, +resulting in the following. [tabs] ====== @@ -553,17 +401,19 @@ honor `@NestedTestConfiguration` semantics. In order to allow development teams to change the default to `OVERRIDE` – for example, for compatibility with Spring Framework 5.0 through 5.2 – the default mode can be changed globally via a JVM system property or a `spring.properties` file in the root of the -classpath. See the xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration["Changing the default enclosing configuration inheritance mode"] - note for details. +classpath. See the +xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration["Changing the default enclosing configuration inheritance mode"] +note for details. Although the following "Hello World" example is very simplistic, it shows how to declare common configuration on a top-level class that is inherited by its `@Nested` test classes. In this particular example, only the `TestConfig` configuration class is inherited. Each nested test class provides its own set of active profiles, resulting in a distinct `ApplicationContext` for each nested test class (see -xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching] for details). Consult the list of -xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[supported annotations] to see -which annotations can be inherited in `@Nested` test classes. +xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching] for details). +Consult the list of +xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[supported annotations] +to see which annotations can be inherited in `@Nested` test classes. [tabs] ====== @@ -626,8 +476,174 @@ Kotlin:: ---- ====== + +[[testcontext-junit4-support]] +== JUnit 4 Support + +[[testcontext-junit4-runner]] +=== Spring JUnit 4 Runner + +The Spring TestContext Framework offers full integration with JUnit 4 through a custom +runner (supported on JUnit 4.12 or higher). By annotating test classes with +`@RunWith(SpringJUnit4ClassRunner.class)` or the shorter `@RunWith(SpringRunner.class)` +variant, developers can implement standard JUnit 4-based unit and integration tests and +simultaneously reap the benefits of the TestContext framework, such as support for +loading application contexts, dependency injection of test instances, transactional test +method execution, and so on. If you want to use the Spring TestContext Framework with an +alternative runner (such as JUnit 4's `Parameterized` runner) or third-party runners +(such as the `MockitoJUnitRunner`), you can, optionally, use +xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's support for JUnit rules] +instead. + +The following code listing shows the minimal requirements for configuring a test class to +run with the custom Spring `Runner`: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes"] +---- + @RunWith(SpringRunner.class) + @TestExecutionListeners({}) + public class SimpleTest { + + @Test + public void testMethod() { + // test logic... + } + } +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes"] +---- + @RunWith(SpringRunner::class) + @TestExecutionListeners + class SimpleTest { + + @Test + fun testMethod() { + // test logic... + } + } +---- +====== + +In the preceding example, `@TestExecutionListeners` is configured with an empty list, to +disable the default listeners, which otherwise would require an `ApplicationContext` to +be configured through `@ContextConfiguration`. + +[[testcontext-junit4-rules]] +=== Spring JUnit 4 Rules + +The `org.springframework.test.context.junit4.rules` package provides the following JUnit +4 rules (supported on JUnit 4.12 or higher): + +* `SpringClassRule` +* `SpringMethodRule` + +`SpringClassRule` is a JUnit `TestRule` that supports class-level features of the Spring +TestContext Framework, whereas `SpringMethodRule` is a JUnit `MethodRule` that supports +instance-level and method-level features of the Spring TestContext Framework. + +In contrast to the `SpringRunner`, Spring's rule-based JUnit support has the advantage of +being independent of any `org.junit.runner.Runner` implementation and can, therefore, be +combined with existing alternative runners (such as JUnit 4's `Parameterized`) or +third-party runners (such as the `MockitoJUnitRunner`). + +To support the full functionality of the TestContext framework, you must combine a +`SpringClassRule` with a `SpringMethodRule`. The following example shows the proper way +to declare these rules in an integration test: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes"] +---- + // Optionally specify a non-Spring Runner via @RunWith(...) + @ContextConfiguration + public class IntegrationTest { + + @ClassRule + public static final SpringClassRule springClassRule = new SpringClassRule(); + + @Rule + public final SpringMethodRule springMethodRule = new SpringMethodRule(); + + @Test + public void testMethod() { + // test logic... + } + } +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes"] +---- + // Optionally specify a non-Spring Runner via @RunWith(...) + @ContextConfiguration + class IntegrationTest { + + @Rule + val springMethodRule = SpringMethodRule() + + @Test + fun testMethod() { + // test logic... + } + + companion object { + @ClassRule + val springClassRule = SpringClassRule() + } + } +---- +====== + +[[testcontext-support-classes-junit4]] +=== JUnit 4 Base Classes + +The `org.springframework.test.context.junit4` package provides the following support +classes for JUnit 4-based test cases (supported on JUnit 4.12 or higher): + +* `AbstractJUnit4SpringContextTests` +* `AbstractTransactionalJUnit4SpringContextTests` + +`AbstractJUnit4SpringContextTests` is an abstract base test class that integrates the +Spring TestContext Framework with explicit `ApplicationContext` testing support in a +JUnit 4 environment. When you extend `AbstractJUnit4SpringContextTests`, you can access a +`protected` `applicationContext` instance variable that you can use to perform explicit +bean lookups or to test the state of the context as a whole. + +`AbstractTransactionalJUnit4SpringContextTests` is an abstract transactional extension of +`AbstractJUnit4SpringContextTests` that adds some convenience functionality for JDBC +access. This class expects a `javax.sql.DataSource` bean and a +`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you +extend `AbstractTransactionalJUnit4SpringContextTests`, you can access a `protected` +`jdbcTemplate` instance variable that you can use to run SQL statements to query the +database. You can use such queries to confirm database state both before and after +running database-related application code, and Spring ensures that such queries run in +the scope of the same transaction as the application code. When used in conjunction with +an ORM tool, be sure to avoid +xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives]. +As mentioned in xref:testing/support-jdbc.adoc[JDBC Testing Support], +`AbstractTransactionalJUnit4SpringContextTests` also provides convenience methods that +delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`. +Furthermore, `AbstractTransactionalJUnit4SpringContextTests` provides an +`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`. + +TIP: These classes are a convenience for extension. If you do not want your test classes +to be tied to a Spring-specific class hierarchy, you can configure your own custom test +classes by using `@RunWith(SpringRunner.class)` or +xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit rules]. + + [[testcontext-support-classes-testng]] -== TestNG Support Classes +== TestNG Support The `org.springframework.test.context.testng` package provides the following support classes for TestNG based test cases: @@ -650,7 +666,8 @@ extend `AbstractTransactionalTestNGSpringContextTests`, you can access a `protec database. You can use such queries to confirm database state both before and after running database-related application code, and Spring ensures that such queries run in the scope of the same transaction as the application code. When used in conjunction with -an ORM tool, be sure to avoid xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives]. +an ORM tool, be sure to avoid +xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives]. As mentioned in xref:testing/support-jdbc.adoc[JDBC Testing Support], `AbstractTransactionalTestNGSpringContextTests` also provides convenience methods that delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`. diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc index cbfcad7a7cec..ba618c0c8d86 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc @@ -234,8 +234,8 @@ Kotlin:: -- URI path patterns can also have embedded `${...}` placeholders that are resolved on startup -through `PropertySourcesPlaceholderConfigurer` against local, system, environment, and -other property sources. You can use this to, for example, parameterize a base URL based on +by using `PropertySourcesPlaceholderConfigurer` against local, system, environment, and +other property sources. You can use this, for example, to parameterize a base URL based on some external configuration. NOTE: Spring WebFlux uses `PathPattern` and the `PathPatternParser` for URI path matching support. diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index a057d3beb163..169d653da8f6 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -7,17 +7,17 @@ javaPlatform { } dependencies { - api(platform("com.fasterxml.jackson:jackson-bom:2.18.3")) - api(platform("io.micrometer:micrometer-bom:1.14.5")) - api(platform("io.netty:netty-bom:4.1.119.Final")) + api(platform("com.fasterxml.jackson:jackson-bom:2.18.4")) + api(platform("io.micrometer:micrometer-bom:1.14.7")) + api(platform("io.netty:netty-bom:4.1.121.Final")) api(platform("io.netty:netty5-bom:5.0.0.Alpha5")) - api(platform("io.projectreactor:reactor-bom:2024.0.4")) + api(platform("io.projectreactor:reactor-bom:2024.0.6")) api(platform("io.rsocket:rsocket-bom:1.1.5")) api(platform("org.apache.groovy:groovy-bom:4.0.26")) api(platform("org.apache.logging.log4j:log4j-bom:2.21.1")) api(platform("org.assertj:assertj-bom:3.27.3")) - api(platform("org.eclipse.jetty:jetty-bom:12.0.18")) - api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.18")) + api(platform("org.eclipse.jetty:jetty-bom:12.0.21")) + api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.21")) api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1")) api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3")) api(platform("org.junit:junit-bom:5.12.2")) @@ -100,7 +100,7 @@ dependencies { api("org.apache.derby:derby:10.16.1.1") api("org.apache.derby:derbyclient:10.16.1.1") api("org.apache.derby:derbytools:10.16.1.1") - api("org.apache.httpcomponents.client5:httpclient5:5.4.3") + api("org.apache.httpcomponents.client5:httpclient5:5.4.4") api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.4") api("org.apache.poi:poi-ooxml:5.2.5") api("org.apache.tomcat.embed:tomcat-embed-core:10.1.28") diff --git a/gradle.properties b/gradle.properties index edd7222db737..266782ebfa4e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=6.2.6-SNAPSHOT +version=6.2.7 org.gradle.caching=true org.gradle.jvmargs=-Xmx2048m diff --git a/gradle/spring-module.gradle b/gradle/spring-module.gradle index 0fb2cfe2fefe..e6378c0739b5 100644 --- a/gradle/spring-module.gradle +++ b/gradle/spring-module.gradle @@ -69,7 +69,7 @@ normalization { javadoc { description = "Generates project-level javadoc for use in -javadoc jar" - + failOnError = true options { encoding = "UTF-8" memberLevel = JavadocMemberLevel.PROTECTED diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 9bbc975c742b..1b33c55baabb 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f853b1c84d..ca025c83a7cc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index faf93008b77e..23d15a936707 100755 --- a/gradlew +++ b/gradlew @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9b42019c7915..5eed7ee84528 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java index 6e37fc17fb46..49f21f9044ba 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -20,8 +20,10 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; +import org.springframework.core.env.AbstractPropertyResolver; import org.springframework.lang.Nullable; import org.springframework.util.StringValueResolver; +import org.springframework.util.SystemPropertyUtils; /** * Abstract base class for property resource configurers that resolve placeholders @@ -37,16 +39,16 @@ * *
  * <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
- *   <property name="driverClassName" value="${driver}" />
- *   <property name="url" value="jdbc:${dbname}" />
+ *   <property name="driverClassName" value="${jdbc.driver}" />
+ *   <property name="url" value="jdbc:${jdbc.dbname}" />
  * </bean>
  * 
* * Example properties file: * *
- * driver=com.mysql.jdbc.Driver
- * dbname=mysql:mydb
+ * jdbc.driver=com.mysql.jdbc.Driver + * jdbc.dbname=mysql:mydb * * Annotated bean definitions may take advantage of property replacement using * the {@link org.springframework.beans.factory.annotation.Value @Value} annotation: @@ -79,11 +81,12 @@ *

Example XML property with default value: * *

- *   <property name="url" value="jdbc:${dbname:defaultdb}" />
+ *   <property name="url" value="jdbc:${jdbc.dbname:defaultdb}" />
  * 
* * @author Chris Beams * @author Juergen Hoeller + * @author Sam Brannen * @since 3.1 * @see PropertyPlaceholderConfigurer * @see org.springframework.context.support.PropertySourcesPlaceholderConfigurer @@ -92,16 +95,21 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi implements BeanNameAware, BeanFactoryAware { /** Default placeholder prefix: {@value}. */ - public static final String DEFAULT_PLACEHOLDER_PREFIX = "${"; + public static final String DEFAULT_PLACEHOLDER_PREFIX = SystemPropertyUtils.PLACEHOLDER_PREFIX; /** Default placeholder suffix: {@value}. */ - public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}"; + public static final String DEFAULT_PLACEHOLDER_SUFFIX = SystemPropertyUtils.PLACEHOLDER_SUFFIX; /** Default value separator: {@value}. */ - public static final String DEFAULT_VALUE_SEPARATOR = ":"; + public static final String DEFAULT_VALUE_SEPARATOR = SystemPropertyUtils.VALUE_SEPARATOR; + + /** + * Default escape character: {@code '\'}. + * @since 6.2 + * @see AbstractPropertyResolver#getDefaultEscapeCharacter() + */ + public static final Character DEFAULT_ESCAPE_CHARACTER = SystemPropertyUtils.ESCAPE_CHARACTER; - /** Default escape character: {@code '\'}. */ - public static final Character DEFAULT_ESCAPE_CHARACTER = '\\'; /** Defaults to {@value #DEFAULT_PLACEHOLDER_PREFIX}. */ protected String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX; @@ -113,9 +121,11 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi @Nullable protected String valueSeparator = DEFAULT_VALUE_SEPARATOR; - /** Defaults to {@link #DEFAULT_ESCAPE_CHARACTER}. */ + /** + * The default is determined by {@link AbstractPropertyResolver#getDefaultEscapeCharacter()}. + */ @Nullable - protected Character escapeCharacter = DEFAULT_ESCAPE_CHARACTER; + protected Character escapeCharacter = AbstractPropertyResolver.getDefaultEscapeCharacter(); protected boolean trimValues = false; @@ -133,7 +143,7 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi /** * Set the prefix that a placeholder string starts with. - * The default is {@value #DEFAULT_PLACEHOLDER_PREFIX}. + *

The default is {@value #DEFAULT_PLACEHOLDER_PREFIX}. */ public void setPlaceholderPrefix(String placeholderPrefix) { this.placeholderPrefix = placeholderPrefix; @@ -141,31 +151,32 @@ public void setPlaceholderPrefix(String placeholderPrefix) { /** * Set the suffix that a placeholder string ends with. - * The default is {@value #DEFAULT_PLACEHOLDER_SUFFIX}. + *

The default is {@value #DEFAULT_PLACEHOLDER_SUFFIX}. */ public void setPlaceholderSuffix(String placeholderSuffix) { this.placeholderSuffix = placeholderSuffix; } /** - * Specify the separating character between the placeholder variable - * and the associated default value, or {@code null} if no such - * special character should be processed as a value separator. - * The default is {@value #DEFAULT_VALUE_SEPARATOR}. + * Specify the separating character between the placeholder variable and the + * associated default value, or {@code null} if no such special character + * should be processed as a value separator. + *

The default is {@value #DEFAULT_VALUE_SEPARATOR}. */ public void setValueSeparator(@Nullable String valueSeparator) { this.valueSeparator = valueSeparator; } /** - * Specify the escape character to use to ignore placeholder prefix - * or value separator, or {@code null} if no escaping should take - * place. - *

Default is {@link #DEFAULT_ESCAPE_CHARACTER}. + * Set the escape character to use to ignore the + * {@linkplain #setPlaceholderPrefix(String) placeholder prefix} and the + * {@linkplain #setValueSeparator(String) value separator}, or {@code null} + * if no escaping should take place. + *

The default is determined by {@link AbstractPropertyResolver#getDefaultEscapeCharacter()}. * @since 6.2 */ - public void setEscapeCharacter(@Nullable Character escsEscapeCharacter) { - this.escapeCharacter = escsEscapeCharacter; + public void setEscapeCharacter(@Nullable Character escapeCharacter) { + this.escapeCharacter = escapeCharacter; } /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyOverrideConfigurer.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyOverrideConfigurer.java index 840a34e76234..0a26f2041965 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyOverrideConfigurer.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyOverrideConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -35,12 +35,14 @@ * * Example properties file: * - *

dataSource.driverClassName=com.mysql.jdbc.Driver
+ * 
+ * dataSource.driverClassName=com.mysql.jdbc.Driver
  * dataSource.url=jdbc:mysql:mydb
* - * In contrast to PropertyPlaceholderConfigurer, the original definition can have default - * values or no values at all for such bean properties. If an overriding properties file does - * not have an entry for a certain bean property, the default context definition is used. + *

In contrast to {@link PropertyPlaceholderConfigurer}, the original definition + * can have default values or no values at all for such bean properties. If an + * overriding properties file does not have an entry for a certain bean property, + * the default context definition is used. * *

Note that the context definition is not aware of being overridden; * so this is not immediately obvious when looking at the XML definition file. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index a46a36c66d39..38887a61a91a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -997,9 +997,17 @@ protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, */ @Nullable private FactoryBean getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) { - boolean locked = this.singletonLock.tryLock(); - if (!locked) { - return null; + Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock(); + if (lockFlag == null) { + this.singletonLock.lock(); + } + else { + boolean locked = (lockFlag && this.singletonLock.tryLock()); + if (!locked) { + // Avoid shortcut FactoryBean instance but allow for subsequent type-based resolution. + resolveBeanClass(mbd, beanName); + return null; + } } try { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 44f24cc912dc..29271d5b0111 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -1066,8 +1066,9 @@ protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName @Nullable protected Boolean isCurrentThreadAllowedToHoldSingletonLock() { String mainThreadPrefix = this.mainThreadPrefix; - if (this.mainThreadPrefix != null) { - // We only differentiate in the preInstantiateSingletons phase. + if (mainThreadPrefix != null) { + // We only differentiate in the preInstantiateSingletons phase, using + // the volatile mainThreadPrefix field as an indicator for that phase. PreInstantiation preInstantiation = this.preInstantiationThread.get(); if (preInstantiation != null) { @@ -1087,7 +1088,7 @@ protected Boolean isCurrentThreadAllowedToHoldSingletonLock() { } else if (this.strictLocking == null) { // No explicit locking configuration -> infer appropriate locking. - if (mainThreadPrefix != null && !getThreadNamePrefix().equals(mainThreadPrefix)) { + if (!getThreadNamePrefix().equals(mainThreadPrefix)) { // An unmanaged thread (assumed to be application-internal) with lenient locking, // and not part of the same thread pool that provided the main bootstrap thread // (excluding scenarios where we are hit by multiple external bootstrap threads). diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index ad3ec147bd5f..a5f8585bc89b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -271,13 +271,15 @@ public Object getSingleton(String beanName, ObjectFactory singletonFactory) { // Fallback as of 6.2: process given singleton bean outside of singleton lock. // Thread-safe exposure is still guaranteed, there is just a risk of collisions // when triggering creation of other beans as dependencies of the current bean. - if (logger.isInfoEnabled()) { - logger.info("Obtaining singleton bean '" + beanName + "' in thread \"" + - Thread.currentThread().getName() + "\" while other thread holds " + - "singleton lock for other beans " + this.singletonsCurrentlyInCreation); - } this.lenientCreationLock.lock(); try { + if (logger.isInfoEnabled()) { + Set lockedBeans = new HashSet<>(this.singletonsCurrentlyInCreation); + lockedBeans.removeAll(this.singletonsInLenientCreation); + logger.info("Obtaining singleton bean '" + beanName + "' in thread \"" + + currentThread.getName() + "\" while other thread holds singleton " + + "lock for other beans " + lockedBeans); + } this.singletonsInLenientCreation.add(beanName); } finally { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java index ffcd87bbfbc1..2ba14e3484db 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -118,7 +118,15 @@ protected Object getCachedObjectForFactoryBean(String beanName) { */ protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName, boolean shouldPostProcess) { if (factory.isSingleton() && containsSingleton(beanName)) { - this.singletonLock.lock(); + Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock(); + boolean locked; + if (lockFlag == null) { + this.singletonLock.lock(); + locked = true; + } + else { + locked = (lockFlag && this.singletonLock.tryLock()); + } try { Object object = this.factoryBeanObjectCache.get(beanName); if (object == null) { @@ -131,11 +139,13 @@ protected Object getObjectFromFactoryBean(FactoryBean factory, String beanNam } else { if (shouldPostProcess) { - if (isSingletonCurrentlyInCreation(beanName)) { - // Temporarily return non-post-processed object, not storing it yet - return object; + if (locked) { + if (isSingletonCurrentlyInCreation(beanName)) { + // Temporarily return non-post-processed object, not storing it yet + return object; + } + beforeSingletonCreation(beanName); } - beforeSingletonCreation(beanName); try { object = postProcessObjectFromFactoryBean(object, beanName); } @@ -144,7 +154,9 @@ protected Object getObjectFromFactoryBean(FactoryBean factory, String beanNam "Post-processing of FactoryBean's singleton object failed", ex); } finally { - afterSingletonCreation(beanName); + if (locked) { + afterSingletonCreation(beanName); + } } } if (containsSingleton(beanName)) { @@ -155,7 +167,9 @@ protected Object getObjectFromFactoryBean(FactoryBean factory, String beanNam return object; } finally { - this.singletonLock.unlock(); + if (locked) { + this.singletonLock.unlock(); + } } } else { diff --git a/spring-context/spring-context.gradle b/spring-context/spring-context.gradle index af48a0fa2070..e4795ee61bb9 100644 --- a/spring-context/spring-context.gradle +++ b/spring-context/spring-context.gradle @@ -59,3 +59,10 @@ dependencies { testRuntimeOnly("org.javamoney:moneta") testRuntimeOnly("org.junit.vintage:junit-vintage-engine") // for @Inject TCK } + +test { + description = "Runs JUnit Jupiter tests and the @Inject TCK via JUnit Vintage." + useJUnitPlatform { + includeEngines "junit-jupiter", "junit-vintage" + } +} diff --git a/spring-context/src/main/java/org/springframework/context/aot/ApplicationContextAotGenerator.java b/spring-context/src/main/java/org/springframework/context/aot/ApplicationContextAotGenerator.java index 0331b145c10e..eb5c736fafb9 100644 --- a/spring-context/src/main/java/org/springframework/context/aot/ApplicationContextAotGenerator.java +++ b/spring-context/src/main/java/org/springframework/context/aot/ApplicationContextAotGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 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. @@ -50,6 +50,7 @@ public class ApplicationContextAotGenerator { */ public ClassName processAheadOfTime(GenericApplicationContext applicationContext, GenerationContext generationContext) { + return withCglibClassHandler(new CglibClassHandler(generationContext), () -> { applicationContext.refreshForAotProcessing(generationContext.getRuntimeHints()); ApplicationContextInitializationCodeGenerator codeGenerator = diff --git a/spring-context/src/main/java/org/springframework/context/aot/ContextAotProcessor.java b/spring-context/src/main/java/org/springframework/context/aot/ContextAotProcessor.java index 16fbee9bb3a1..746e23cfd6f9 100644 --- a/spring-context/src/main/java/org/springframework/context/aot/ContextAotProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/aot/ContextAotProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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. @@ -80,8 +80,9 @@ protected Class getApplicationClass() { @Override protected ClassName doProcess() { deleteExistingOutput(); - GenericApplicationContext applicationContext = prepareApplicationContext(getApplicationClass()); - return performAotProcessing(applicationContext); + try (GenericApplicationContext applicationContext = prepareApplicationContext(getApplicationClass())) { + return performAotProcessing(applicationContext); + } } /** diff --git a/spring-context/src/main/java/org/springframework/context/aot/ReflectiveProcessorAotContributionBuilder.java b/spring-context/src/main/java/org/springframework/context/aot/ReflectiveProcessorAotContributionBuilder.java index 4b6a7b79182a..d538a56ede36 100644 --- a/spring-context/src/main/java/org/springframework/context/aot/ReflectiveProcessorAotContributionBuilder.java +++ b/spring-context/src/main/java/org/springframework/context/aot/ReflectiveProcessorAotContributionBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -100,6 +100,7 @@ public BeanFactoryInitializationAotContribution build() { return (!this.classes.isEmpty() ? new AotContribution(this.classes) : null); } + private static class AotContribution implements BeanFactoryInitializationAotContribution { private final Class[] classes; @@ -113,9 +114,9 @@ public void applyTo(GenerationContext generationContext, BeanFactoryInitializati RuntimeHints runtimeHints = generationContext.getRuntimeHints(); registrar.registerRuntimeHints(runtimeHints, this.classes); } - } + private static class ReflectiveClassPathScanner extends ClassPathScanningCandidateComponentProvider { @Nullable diff --git a/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java b/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java index 31d1652542b8..7df8bf086ae5 100644 --- a/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java +++ b/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -29,7 +29,6 @@ import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertiesPropertySource; -import org.springframework.core.env.PropertyResolver; import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySources; import org.springframework.core.env.PropertySourcesPropertyResolver; @@ -49,7 +48,7 @@ * XSD documentation for complete details. * *

Any local properties (for example, those added via {@link #setProperties}, {@link #setLocations} - * et al.) are added as a {@code PropertySource}. Search precedence of local properties is + * et al.) are added as a single {@link PropertySource}. Search precedence of local properties is * based on the value of the {@link #setLocalOverride localOverride} property, which is by * default {@code false} meaning that local properties are to be searched last, after all * environment property sources. @@ -101,8 +100,9 @@ public void setPropertySources(PropertySources propertySources) { } /** - * {@code PropertySources} from the given {@link Environment} - * will be searched when replacing ${...} placeholders. + * {@inheritDoc} + *

{@code PropertySources} from the given {@link Environment} will be searched + * when replacing ${...} placeholders. * @see #setPropertySources * @see #postProcessBeanFactory */ @@ -132,28 +132,11 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) if (this.propertySources == null) { this.propertySources = new MutablePropertySources(); if (this.environment != null) { - PropertyResolver propertyResolver = this.environment; - // If the ignoreUnresolvablePlaceholders flag is set to true, we have to create a - // local PropertyResolver to enforce that setting, since the Environment is most - // likely not configured with ignoreUnresolvablePlaceholders set to true. - // See https://github.com/spring-projects/spring-framework/issues/27947 - if (this.ignoreUnresolvablePlaceholders && - (this.environment instanceof ConfigurableEnvironment configurableEnvironment)) { - PropertySourcesPropertyResolver resolver = - new PropertySourcesPropertyResolver(configurableEnvironment.getPropertySources()); - resolver.setIgnoreUnresolvableNestedPlaceholders(true); - propertyResolver = resolver; - } - PropertyResolver propertyResolverToUse = propertyResolver; - this.propertySources.addLast( - new PropertySource<>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) { - @Override - @Nullable - public String getProperty(String key) { - return propertyResolverToUse.getProperty(key); - } - } - ); + PropertySource environmentPropertySource = + (this.environment instanceof ConfigurableEnvironment configurableEnvironment ? + new ConfigurableEnvironmentPropertySource(configurableEnvironment) : + new FallbackEnvironmentPropertySource(this.environment)); + this.propertySources.addLast(environmentPropertySource); } try { PropertySource localPropertySource = @@ -176,6 +159,7 @@ public String getProperty(String key) { /** * Create a {@link ConfigurablePropertyResolver} for the specified property sources. + *

The default implementation creates a {@link PropertySourcesPropertyResolver}. * @param propertySources the property sources to use * @since 6.0.12 */ @@ -188,7 +172,7 @@ protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySou * placeholders with values from the given properties. */ protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, - final ConfigurablePropertyResolver propertyResolver) throws BeansException { + ConfigurablePropertyResolver propertyResolver) throws BeansException { propertyResolver.setPlaceholderPrefix(this.placeholderPrefix); propertyResolver.setPlaceholderSuffix(this.placeholderSuffix); @@ -234,4 +218,75 @@ public PropertySources getAppliedPropertySources() throws IllegalStateException return this.appliedPropertySources; } + + /** + * Custom {@link PropertySource} that delegates to the + * {@link ConfigurableEnvironment#getPropertySources() PropertySources} in a + * {@link ConfigurableEnvironment}. + * @since 6.2.7 + */ + private static class ConfigurableEnvironmentPropertySource extends PropertySource { + + ConfigurableEnvironmentPropertySource(ConfigurableEnvironment environment) { + super(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, environment); + } + + @Override + public boolean containsProperty(String name) { + for (PropertySource propertySource : super.source.getPropertySources()) { + if (propertySource.containsProperty(name)) { + return true; + } + } + return false; + } + + @Override + @Nullable + public Object getProperty(String name) { + for (PropertySource propertySource : super.source.getPropertySources()) { + Object candidate = propertySource.getProperty(name); + if (candidate != null) { + return candidate; + } + } + return null; + } + + @Override + public String toString() { + return "ConfigurableEnvironmentPropertySource {propertySources=" + super.source.getPropertySources() + "}"; + } + } + + + /** + * Fallback {@link PropertySource} that delegates to a raw {@link Environment}. + *

Should never apply in a regular scenario, since the {@code Environment} + * in an {@code ApplicationContext} should always be a {@link ConfigurableEnvironment}. + * @since 6.2.7 + */ + private static class FallbackEnvironmentPropertySource extends PropertySource { + + FallbackEnvironmentPropertySource(Environment environment) { + super(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, environment); + } + + @Override + public boolean containsProperty(String name) { + return super.source.containsProperty(name); + } + + @Override + @Nullable + public Object getProperty(String name) { + return super.source.getProperty(name); + } + + @Override + public String toString() { + return "FallbackEnvironmentPropertySource {environment=" + super.source + "}"; + } + } + } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/EnableAsync.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/EnableAsync.java index 38f5c2f410cb..cced791405ec 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/EnableAsync.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/EnableAsync.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2025 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. @@ -146,6 +146,12 @@ * compile-time weaving or load-time weaving applying the aspect to the affected classes. * There is no proxy involved in such a scenario; local calls will be intercepted as well. * + *

Note: {@code @EnableAsync} applies to its local application context only, + * allowing for selective activation at different levels. Please redeclare + * {@code @EnableAsync} in each individual context, for example, the common root web + * application context and any separate {@code DispatcherServlet} application contexts, + * if you need to apply its behavior at multiple levels. + * * @author Chris Beams * @author Juergen Hoeller * @author Stephane Nicoll diff --git a/spring-context/src/main/java/org/springframework/validation/DataBinder.java b/spring-context/src/main/java/org/springframework/validation/DataBinder.java index 3433af095bd1..ab134ce3b165 100644 --- a/spring-context/src/main/java/org/springframework/validation/DataBinder.java +++ b/spring-context/src/main/java/org/springframework/validation/DataBinder.java @@ -27,7 +27,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -550,14 +549,13 @@ public String[] getAllowedFields() { *

Mark fields as disallowed, for example to avoid unwanted * modifications by malicious users when binding HTTP request parameters. *

Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and - * {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as - * well as direct equality. - *

The default implementation of this method stores disallowed field patterns - * in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical} - * form and also transforms disallowed field patterns to - * {@linkplain String#toLowerCase() lowercase} to support case-insensitive - * pattern matching in {@link #isAllowed}. Subclasses which override this - * method must therefore take both of these transformations into account. + * {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), + * as well as direct equality. + *

The default implementation of this method stores disallowed field + * patterns in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) + * canonical} form, and subsequently pattern matching in {@link #isAllowed} + * is case-insensitive. Subclasses that override this method must therefore + * take this transformation into account. *

More sophisticated matching can be implemented by overriding the * {@link #isAllowed} method. *

Alternatively, specify a list of allowed field patterns. @@ -575,8 +573,7 @@ public void setDisallowedFields(@Nullable String... disallowedFields) { else { String[] fieldPatterns = new String[disallowedFields.length]; for (int i = 0; i < fieldPatterns.length; i++) { - String field = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]); - fieldPatterns[i] = field.toLowerCase(Locale.ROOT); + fieldPatterns[i] = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]); } this.disallowedFields = fieldPatterns; } @@ -1302,9 +1299,9 @@ protected void checkAllowedFields(MutablePropertyValues mpvs) { * Determine if the given field is allowed for binding. *

Invoked for each passed-in property value. *

Checks for {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and - * {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as - * well as direct equality, in the configured lists of allowed field patterns - * and disallowed field patterns. + * {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), + * as well as direct equality, in the configured lists of allowed field + * patterns and disallowed field patterns. *

Matching against allowed field patterns is case-sensitive; whereas, * matching against disallowed field patterns is case-insensitive. *

A field matching a disallowed pattern will not be accepted even if it @@ -1320,8 +1317,13 @@ protected void checkAllowedFields(MutablePropertyValues mpvs) { protected boolean isAllowed(String field) { String[] allowed = getAllowedFields(); String[] disallowed = getDisallowedFields(); - return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) && - (ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field.toLowerCase(Locale.ROOT)))); + if (!ObjectUtils.isEmpty(allowed) && !PatternMatchUtils.simpleMatch(allowed, field)) { + return false; + } + if (!ObjectUtils.isEmpty(disallowed)) { + return !PatternMatchUtils.simpleMatchIgnoreCase(disallowed, field); + } + return true; } /** diff --git a/spring-context/src/main/resources/org/springframework/remoting/rmi/RmiInvocationWrapperRTD.xml b/spring-context/src/main/resources/org/springframework/remoting/rmi/RmiInvocationWrapperRTD.xml deleted file mode 100644 index 3ea5d627e90c..000000000000 --- a/spring-context/src/main/resources/org/springframework/remoting/rmi/RmiInvocationWrapperRTD.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - diff --git a/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java b/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java index 75f446f6ad3e..ed5e45b85b85 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java @@ -24,6 +24,7 @@ import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCurrentlyInCreationException; +import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -243,14 +244,24 @@ public TestBean testBean3(TestBean testBean4) { } @Bean - public TestBean testBean4() { + public FactoryBean testBean4() { try { Thread.sleep(2000); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } - return new TestBean(); + TestBean testBean = new TestBean(); + return new FactoryBean<>() { + @Override + public TestBean getObject() { + return testBean; + } + @Override + public Class getObjectType() { + return testBean.getClass(); + } + }; } } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/jsr330/SpringAtInjectTckTests.java b/spring-context/src/test/java/org/springframework/context/annotation/jsr330/SpringAtInjectTckTests.java index f9d0574d552a..bc36d8fd46f6 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/jsr330/SpringAtInjectTckTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/jsr330/SpringAtInjectTckTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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,7 +38,8 @@ * @author Juergen Hoeller * @since 3.0 */ -class SpringAtInjectTckTests { +// WARNING: This class MUST be public, since it is based on JUnit 3. +public class SpringAtInjectTckTests { @SuppressWarnings("unchecked") public static Test suite() { diff --git a/spring-context/src/test/java/org/springframework/context/aot/ContextAotProcessorTests.java b/spring-context/src/test/java/org/springframework/context/aot/ContextAotProcessorTests.java index 384f54d59fcc..f41711797fa1 100644 --- a/spring-context/src/test/java/org/springframework/context/aot/ContextAotProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/aot/ContextAotProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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. @@ -45,8 +45,9 @@ class ContextAotProcessorTests { void processGeneratesAssets(@TempDir Path directory) { GenericApplicationContext context = new AnnotationConfigApplicationContext(); context.registerBean(SampleApplication.class); - ContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class, directory); + DemoContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class, directory); ClassName className = processor.process(); + assertThat(processor.context.isClosed()).isTrue(); assertThat(className).isEqualTo(ClassName.get(SampleApplication.class.getPackageName(), "ContextAotProcessorTests_SampleApplication__ApplicationContextInitializer")); assertThat(directory).satisfies(hasGeneratedAssetsForSampleApplication()); @@ -61,9 +62,10 @@ void processingDeletesExistingOutput(@TempDir Path directory) throws IOException Path existingSourceOutput = createExisting(sourceOutput); Path existingResourceOutput = createExisting(resourceOutput); Path existingClassOutput = createExisting(classOutput); - ContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class, + DemoContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class, sourceOutput, resourceOutput, classOutput); processor.process(); + assertThat(processor.context.isClosed()).isTrue(); assertThat(existingSourceOutput).doesNotExist(); assertThat(existingResourceOutput).doesNotExist(); assertThat(existingClassOutput).doesNotExist(); @@ -73,13 +75,14 @@ void processingDeletesExistingOutput(@TempDir Path directory) throws IOException void processWithEmptyNativeImageArgumentsDoesNotCreateNativeImageProperties(@TempDir Path directory) { GenericApplicationContext context = new AnnotationConfigApplicationContext(); context.registerBean(SampleApplication.class); - ContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class, directory) { + DemoContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class, directory) { @Override protected List getDefaultNativeImageArguments(String application) { return Collections.emptyList(); } }; processor.process(); + assertThat(processor.context.isClosed()).isTrue(); assertThat(directory.resolve("resource/META-INF/native-image/com.example/example/native-image.properties")) .doesNotExist(); context.close(); @@ -118,6 +121,8 @@ private Consumer hasGeneratedAssetsForSampleApplication() { private static class DemoContextAotProcessor extends ContextAotProcessor { + AnnotationConfigApplicationContext context; + DemoContextAotProcessor(Class application, Path rootPath) { this(application, rootPath.resolve("source"), rootPath.resolve("resource"), rootPath.resolve("class")); } @@ -141,11 +146,12 @@ private static Settings createSettings(Path sourceOutput, Path resourceOutput, protected GenericApplicationContext prepareApplicationContext(Class application) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(application); + this.context = context; return context; } - } + @Configuration(proxyBeanMethods = false) static class SampleApplication { @@ -153,7 +159,6 @@ static class SampleApplication { public String testBean() { return "Hello"; } - } } diff --git a/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java b/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java index c8bb5d5ef708..d78bb08406a4 100644 --- a/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java +++ b/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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,20 +16,34 @@ package org.springframework.context.support; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Optional; import java.util.Properties; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.core.SpringProperties; import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.env.AbstractPropertyResolver; +import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.core.env.StandardEnvironment; @@ -38,12 +52,15 @@ import org.springframework.core.testfixture.env.MockPropertySource; import org.springframework.mock.env.MockEnvironment; import org.springframework.util.PlaceholderResolutionException; +import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition; import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition; +import static org.springframework.core.env.AbstractPropertyResolver.DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME; /** * Tests for {@link PropertySourcesPlaceholderConfigurer}. @@ -73,6 +90,43 @@ void replacementFromEnvironmentProperties() { assertThat(ppc.getAppliedPropertySources()).isNotNull(); } + /** + * Ensure that a {@link PropertySource} added to the {@code Environment} after context + * refresh (i.e., after {@link PropertySourcesPlaceholderConfigurer#postProcessBeanFactory()} + * has been invoked) can still contribute properties in late-binding scenarios. + */ + @Test // gh-34861 + void replacementFromEnvironmentPropertiesWithLateBinding() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + MutablePropertySources propertySources = context.getEnvironment().getPropertySources(); + propertySources.addFirst(new MockPropertySource("early properties").withProperty("foo", "bar")); + + context.register(PropertySourcesPlaceholderConfigurer.class); + context.register(PrototypeBean.class); + context.refresh(); + + // Verify that placeholder resolution works for early binding. + PrototypeBean prototypeBean = context.getBean(PrototypeBean.class); + assertThat(prototypeBean.getName()).isEqualTo("bar"); + assertThat(prototypeBean.isJedi()).isFalse(); + + // Add new PropertySource after context refresh. + propertySources.addFirst(new MockPropertySource("late properties").withProperty("jedi", "true")); + + // Verify that placeholder resolution works for late binding: isJedi() switches to true. + prototypeBean = context.getBean(PrototypeBean.class); + assertThat(prototypeBean.getName()).isEqualTo("bar"); + assertThat(prototypeBean.isJedi()).isTrue(); + + // Add yet another PropertySource after context refresh. + propertySources.addFirst(new MockPropertySource("even later properties").withProperty("foo", "enigma")); + + // Verify that placeholder resolution works for even later binding: getName() switches to enigma. + prototypeBean = context.getBean(PrototypeBean.class); + assertThat(prototypeBean.getName()).isEqualTo("enigma"); + assertThat(prototypeBean.isJedi()).isTrue(); + } + @Test void localPropertiesViaResource() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -88,14 +142,29 @@ void localPropertiesViaResource() { assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("foo"); } - @Test - void localPropertiesOverrideFalse() { - localPropertiesOverride(false); - } + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void localPropertiesOverride(boolean override) { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean", + genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${foo}") + .getBeanDefinition()); - @Test - void localPropertiesOverrideTrue() { - localPropertiesOverride(true); + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + + ppc.setLocalOverride(override); + ppc.setProperties(new Properties() {{ + setProperty("foo", "local"); + }}); + ppc.setEnvironment(new MockEnvironment().withProperty("foo", "enclosing")); + ppc.postProcessBeanFactory(bf); + if (override) { + assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("local"); + } + else { + assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("enclosing"); + } } @Test @@ -281,28 +350,58 @@ public Object getProperty(String key) { assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("bar"); } - @SuppressWarnings("serial") - private void localPropertiesOverride(boolean override) { + @Test // gh-34861 + void withEnumerableAndNonEnumerablePropertySourcesInTheEnvironmentAndLocalProperties() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.registerBeanDefinition("testBean", genericBeanDefinition(TestBean.class) - .addPropertyValue("name", "${foo}") + .addPropertyValue("name", "${foo:bogus}") + .addPropertyValue("jedi", "${local:false}") .getBeanDefinition()); - PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + // 1) MockPropertySource is an EnumerablePropertySource. + MockPropertySource mockPropertySource = new MockPropertySource("mockPropertySource") + .withProperty("foo", "${bar}"); - ppc.setLocalOverride(override); + // 2) PropertySource is not an EnumerablePropertySource. + PropertySource rawPropertySource = new PropertySource<>("rawPropertySource", new Object()) { + @Override + public Object getProperty(String key) { + return ("bar".equals(key) ? "quux" : null); + } + }; + + MockEnvironment env = new MockEnvironment(); + env.getPropertySources().addFirst(mockPropertySource); + env.getPropertySources().addLast(rawPropertySource); + + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + ppc.setEnvironment(env); + // 3) Local properties are stored in a PropertiesPropertySource which is an EnumerablePropertySource. ppc.setProperties(new Properties() {{ - setProperty("foo", "local"); + setProperty("local", "true"); }}); - ppc.setEnvironment(new MockEnvironment().withProperty("foo", "enclosing")); ppc.postProcessBeanFactory(bf); - if (override) { - assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("local"); - } - else { - assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("enclosing"); + + // Verify all properties can be resolved via the Environment. + assertThat(env.getProperty("foo")).isEqualTo("quux"); + assertThat(env.getProperty("bar")).isEqualTo("quux"); + + // Verify that placeholder resolution works. + TestBean testBean = bf.getBean(TestBean.class); + assertThat(testBean.getName()).isEqualTo("quux"); + assertThat(testBean.isJedi()).isTrue(); + + // Verify that the presence of a non-EnumerablePropertySource does not prevent + // accessing EnumerablePropertySources via getAppliedPropertySources(). + List propertyNames = new ArrayList<>(); + for (PropertySource propertySource : ppc.getAppliedPropertySources()) { + if (propertySource instanceof EnumerablePropertySource enumerablePropertySource) { + Collections.addAll(propertyNames, enumerablePropertySource.getPropertyNames()); + } } + // Should not contain "foo" or "bar" from the Environment. + assertThat(propertyNames).containsOnly("local"); } @Test @@ -432,6 +531,252 @@ void optionalPropertyWithoutValue() { } + /** + * Tests that use the escape character (or disable it) with nested placeholder + * resolution. + */ + @Nested + class EscapedNestedPlaceholdersTests { + + @Test // gh-34861 + void singleEscapeWithDefaultEscapeCharacter() { + MockEnvironment env = new MockEnvironment() + .withProperty("user.home", "admin") + .withProperty("my.property", "\\DOMAIN\\${user.home}"); + + DefaultListableBeanFactory bf = createBeanFactory(); + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + ppc.setEnvironment(env); + ppc.postProcessBeanFactory(bf); + + // \DOMAIN\${user.home} resolves to \DOMAIN${user.home} instead of \DOMAIN\admin + assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("\\DOMAIN${user.home}"); + } + + @Test // gh-34861 + void singleEscapeWithCustomEscapeCharacter() { + MockEnvironment env = new MockEnvironment() + .withProperty("user.home", "admin\\~${nested}") + .withProperty("my.property", "DOMAIN\\${user.home}\\~${enigma}"); + + DefaultListableBeanFactory bf = createBeanFactory(); + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + ppc.setEnvironment(env); + // Set custom escape character. + ppc.setEscapeCharacter('~'); + ppc.postProcessBeanFactory(bf); + + assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("DOMAIN\\admin\\${nested}\\${enigma}"); + } + + @Test // gh-34861 + void singleEscapeWithEscapeCharacterDisabled() { + MockEnvironment env = new MockEnvironment() + .withProperty("user.home", "admin\\") + .withProperty("my.property", "\\DOMAIN\\${user.home}"); + + DefaultListableBeanFactory bf = createBeanFactory(); + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + ppc.setEnvironment(env); + // Disable escape character. + ppc.setEscapeCharacter(null); + ppc.postProcessBeanFactory(bf); + + // \DOMAIN\${user.home} resolves to \DOMAIN\admin + assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("\\DOMAIN\\admin\\"); + } + + @Test // gh-34861 + void tripleEscapeWithDefaultEscapeCharacter() { + MockEnvironment env = new MockEnvironment() + .withProperty("user.home", "admin\\\\\\") + .withProperty("my.property", "DOMAIN\\\\\\${user.home}#${user.home}"); + + DefaultListableBeanFactory bf = createBeanFactory(); + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + ppc.setEnvironment(env); + ppc.postProcessBeanFactory(bf); + + assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("DOMAIN\\\\${user.home}#admin\\\\\\"); + } + + @Test // gh-34861 + void tripleEscapeWithCustomEscapeCharacter() { + MockEnvironment env = new MockEnvironment() + .withProperty("user.home", "admin\\~${enigma}") + .withProperty("my.property", "DOMAIN~~~${user.home}#${user.home}"); + + DefaultListableBeanFactory bf = createBeanFactory(); + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + ppc.setEnvironment(env); + // Set custom escape character. + ppc.setEscapeCharacter('~'); + ppc.postProcessBeanFactory(bf); + + assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("DOMAIN~~${user.home}#admin\\${enigma}"); + } + + @Test // gh-34861 + void singleEscapeWithDefaultEscapeCharacterAndIgnoreUnresolvablePlaceholders() { + MockEnvironment env = new MockEnvironment() + .withProperty("user.home", "${enigma}") + .withProperty("my.property", "\\${DOMAIN}${user.home}"); + + DefaultListableBeanFactory bf = createBeanFactory(); + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + ppc.setEnvironment(env); + ppc.setIgnoreUnresolvablePlaceholders(true); + ppc.postProcessBeanFactory(bf); + + assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("${DOMAIN}${enigma}"); + } + + @Test // gh-34861 + void singleEscapeWithCustomEscapeCharacterAndIgnoreUnresolvablePlaceholders() { + MockEnvironment env = new MockEnvironment() + .withProperty("user.home", "${enigma}") + .withProperty("my.property", "~${DOMAIN}\\${user.home}"); + + DefaultListableBeanFactory bf = createBeanFactory(); + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + ppc.setEnvironment(env); + // Set custom escape character. + ppc.setEscapeCharacter('~'); + ppc.setIgnoreUnresolvablePlaceholders(true); + ppc.postProcessBeanFactory(bf); + + assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("${DOMAIN}\\${enigma}"); + } + + @Test // gh-34861 + void tripleEscapeWithDefaultEscapeCharacterAndIgnoreUnresolvablePlaceholders() { + MockEnvironment env = new MockEnvironment() + .withProperty("user.home", "${enigma}") + .withProperty("my.property", "X:\\\\\\${DOMAIN}${user.home}"); + + DefaultListableBeanFactory bf = createBeanFactory(); + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + ppc.setEnvironment(env); + ppc.setIgnoreUnresolvablePlaceholders(true); + ppc.postProcessBeanFactory(bf); + + assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("X:\\\\${DOMAIN}${enigma}"); + } + + private static DefaultListableBeanFactory createBeanFactory() { + BeanDefinition beanDefinition = genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${my.property}") + .getBeanDefinition(); + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean",beanDefinition); + return bf; + } + + } + + + /** + * Tests that globally set the default escape character (or disable it) and + * rely on nested placeholder resolution. + */ + @Nested + class GlobalDefaultEscapeCharacterTests { + + private static final Field defaultEscapeCharacterField = + ReflectionUtils.findField(AbstractPropertyResolver.class, "defaultEscapeCharacter"); + + static { + ReflectionUtils.makeAccessible(defaultEscapeCharacterField); + } + + + @BeforeEach + void resetStateBeforeEachTest() { + resetState(); + } + + @AfterAll + static void resetState() { + ReflectionUtils.setField(defaultEscapeCharacterField, null, Character.MIN_VALUE); + setSpringProperty(null); + } + + + @Test // gh-34865 + void defaultEscapeCharacterSetToXyz() { + setSpringProperty("XYZ"); + + assertThatIllegalArgumentException() + .isThrownBy(PropertySourcesPlaceholderConfigurer::new) + .withMessage("Value [XYZ] for property [%s] must be a single character or an empty string", + DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME); + } + + @Test // gh-34865 + void defaultEscapeCharacterDisabled() { + setSpringProperty(""); + + MockEnvironment env = new MockEnvironment() + .withProperty("user.home", "admin") + .withProperty("my.property", "\\DOMAIN\\${user.home}"); + + DefaultListableBeanFactory bf = createBeanFactory(); + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + ppc.setEnvironment(env); + ppc.postProcessBeanFactory(bf); + + assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("\\DOMAIN\\admin"); + } + + @Test // gh-34865 + void defaultEscapeCharacterSetToBackslash() { + setSpringProperty("\\"); + + MockEnvironment env = new MockEnvironment() + .withProperty("user.home", "admin") + .withProperty("my.property", "\\DOMAIN\\${user.home}"); + + DefaultListableBeanFactory bf = createBeanFactory(); + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + ppc.setEnvironment(env); + ppc.postProcessBeanFactory(bf); + + // \DOMAIN\${user.home} resolves to \DOMAIN${user.home} instead of \DOMAIN\admin + assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("\\DOMAIN${user.home}"); + } + + @Test // gh-34865 + void defaultEscapeCharacterSetToTilde() { + setSpringProperty("~"); + + MockEnvironment env = new MockEnvironment() + .withProperty("user.home", "admin\\~${nested}") + .withProperty("my.property", "DOMAIN\\${user.home}\\~${enigma}"); + + DefaultListableBeanFactory bf = createBeanFactory(); + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + ppc.setEnvironment(env); + ppc.postProcessBeanFactory(bf); + + assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("DOMAIN\\admin\\${nested}\\${enigma}"); + } + + private static void setSpringProperty(String value) { + SpringProperties.setProperty(DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME, value); + } + + private static DefaultListableBeanFactory createBeanFactory() { + BeanDefinition beanDefinition = genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${my.property}") + .getBeanDefinition(); + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean",beanDefinition); + return bf; + } + + } + + private static class OptionalTestBean { private Optional name; @@ -472,4 +817,23 @@ static PropertySourcesPlaceholderConfigurer pspc() { } } + @Scope(BeanDefinition.SCOPE_PROTOTYPE) + static class PrototypeBean { + + @Value("${foo:bogus}") + private String name; + + @Value("${jedi:false}") + private boolean jedi; + + + public String getName() { + return this.name; + } + + public boolean isJedi() { + return this.jedi; + } + } + } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java b/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java index fd4077b78b19..bbbdcbaeee32 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java @@ -463,10 +463,21 @@ public static Class defineClass(String className, byte[] b, ClassLoader loader, c = lookup.defineClass(b); } catch (LinkageError | IllegalArgumentException ex) { - // in case of plain LinkageError (class already defined) - // or IllegalArgumentException (class in different package): - // fall through to traditional ClassLoader.defineClass below - t = ex; + if (ex instanceof LinkageError) { + // Could be a ClassLoader mismatch with the class pre-existing in a + // parent ClassLoader -> try loadClass before giving up completely. + try { + c = contextClass.getClassLoader().loadClass(className); + } + catch (ClassNotFoundException cnfe) { + } + } + if (c == null) { + // in case of plain LinkageError (class already defined) + // or IllegalArgumentException (class in different package): + // fall through to traditional ClassLoader.defineClass below + t = ex; + } } catch (Throwable ex) { throw new CodeGenerationException(ex); diff --git a/spring-core/src/main/java/org/springframework/core/SpringProperties.java b/spring-core/src/main/java/org/springframework/core/SpringProperties.java index 1bb44d6cd264..209956fe72b2 100644 --- a/spring-core/src/main/java/org/springframework/core/SpringProperties.java +++ b/spring-core/src/main/java/org/springframework/core/SpringProperties.java @@ -26,18 +26,20 @@ /** * Static holder for local Spring properties, i.e. defined at the Spring library level. * - *

Reads a {@code spring.properties} file from the root of the Spring library classpath, - * and also allows for programmatically setting properties through {@link #setProperty}. - * When checking a property, local entries are being checked first, then falling back - * to JVM-level system properties through a {@link System#getProperty} check. + *

Reads a {@code spring.properties} file from the root of the classpath and + * also allows for programmatically setting properties via {@link #setProperty}. + * When retrieving properties, local entries are checked first, with JVM-level + * system properties checked next as a fallback via {@link System#getProperty}. * *

This is an alternative way to set Spring-related system properties such as - * "spring.getenv.ignore" and "spring.beaninfo.ignore", in particular for scenarios - * where JVM system properties are locked on the target platform (for example, WebSphere). - * See {@link #setFlag} for a convenient way to locally set such flags to "true". + * {@code spring.getenv.ignore} and {@code spring.beaninfo.ignore}, in particular + * for scenarios where JVM system properties are locked on the target platform + * (for example, WebSphere). See {@link #setFlag} for a convenient way to locally + * set such flags to {@code "true"}. * * @author Juergen Hoeller * @since 3.2.7 + * @see org.springframework.aot.AotDetector#AOT_ENABLED * @see org.springframework.beans.StandardBeanInfoFactory#IGNORE_BEANINFO_PROPERTY_NAME * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#STRICT_LOCKING_PROPERTY_NAME * @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java index 918a63ee5554..fa92e4ef98de 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java @@ -361,16 +361,12 @@ private static boolean isOverride(Method rootMethod, Method candidateMethod) { } private static boolean hasSameParameterTypes(Method rootMethod, Method candidateMethod) { - if (candidateMethod.getParameterCount() != rootMethod.getParameterCount()) { - return false; - } Class[] rootParameterTypes = rootMethod.getParameterTypes(); Class[] candidateParameterTypes = candidateMethod.getParameterTypes(); if (Arrays.equals(candidateParameterTypes, rootParameterTypes)) { return true; } - return hasSameGenericTypeParameters(rootMethod, candidateMethod, - rootParameterTypes); + return hasSameGenericTypeParameters(rootMethod, candidateMethod, rootParameterTypes); } private static boolean hasSameGenericTypeParameters( diff --git a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java index de1e84c535b3..f45241726d65 100644 --- a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java +++ b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -42,7 +42,7 @@ * add by default. {@code AbstractEnvironment} adds none. Subclasses should contribute * property sources through the protected {@link #customizePropertySources(MutablePropertySources)} * hook, while clients should customize using {@link ConfigurableEnvironment#getPropertySources()} - * and working against the {@link MutablePropertySources} API. + * and work against the {@link MutablePropertySources} API. * See {@link ConfigurableEnvironment} javadoc for usage examples. * * @author Chris Beams @@ -66,7 +66,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore"; /** - * Name of the property to set to specify active profiles: {@value}. + * Name of the property to specify active profiles: {@value}. *

The value may be comma delimited. *

Note that certain shell environments such as Bash disallow the use of the period * character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource} @@ -77,7 +77,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active"; /** - * Name of the property to set to specify profiles that are active by default: {@value}. + * Name of the property to specify profiles that are active by default: {@value}. *

The value may be comma delimited. *

Note that certain shell environments such as Bash disallow the use of the period * character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource} @@ -141,7 +141,7 @@ protected AbstractEnvironment(MutablePropertySources propertySources) { /** * Factory method used to create the {@link ConfigurablePropertyResolver} - * instance used by the Environment. + * used by this {@code Environment}. * @since 5.3.4 * @see #getPropertyResolver() */ @@ -150,8 +150,7 @@ protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySou } /** - * Return the {@link ConfigurablePropertyResolver} being used by the - * {@link Environment}. + * Return the {@link ConfigurablePropertyResolver} used by the {@code Environment}. * @since 5.3.4 * @see #createPropertyResolver(MutablePropertySources) */ @@ -320,7 +319,6 @@ public void addActiveProfile(String profile) { } } - @Override public String[] getDefaultProfiles() { return StringUtils.toStringArray(doGetDefaultProfiles()); @@ -328,7 +326,7 @@ public String[] getDefaultProfiles() { /** * Return the set of default profiles explicitly set via - * {@link #setDefaultProfiles(String...)} or if the current set of default profiles + * {@link #setDefaultProfiles(String...)}, or if the current set of default profiles * consists only of {@linkplain #getReservedDefaultProfiles() reserved default * profiles}, then check for the presence of {@link #doGetActiveProfilesProperty()} * and assign its value (if any) to the set of default profiles. @@ -420,7 +418,7 @@ protected boolean isProfileActive(String profile) { * active or default profiles. *

Subclasses may override to impose further restrictions on profile syntax. * @throws IllegalArgumentException if the profile is null, empty, whitespace-only or - * begins with the profile NOT operator (!). + * begins with the profile NOT operator (!) * @see #acceptsProfiles * @see #addActiveProfile * @see #setDefaultProfiles diff --git a/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java index 151c482b7ed9..a13c40a74fba 100644 --- a/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java +++ b/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -23,6 +23,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.core.SpringProperties; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; @@ -37,10 +38,52 @@ * * @author Chris Beams * @author Juergen Hoeller + * @author Sam Brannen * @since 3.1 */ public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver { + /** + * JVM system property used to change the default escape character + * for property placeholder support: {@value}. + *

To configure a custom escape character, supply a string containing a + * single character (other than {@link Character#MIN_VALUE}). For example, + * supplying the following JVM system property via the command line sets the + * default escape character to {@code '@'}. + *

-Dspring.placeholder.escapeCharacter.default=@
+ *

To disable escape character support, set the value to an empty string + * — for example, by supplying the following JVM system property via + * the command line. + *

-Dspring.placeholder.escapeCharacter.default=
+ *

If the property is not set, {@code '\'} will be used as the default + * escape character. + *

May alternatively be configured via a + * {@link org.springframework.core.SpringProperties spring.properties} file + * in the root of the classpath. + * @since 6.2.7 + * @see #getDefaultEscapeCharacter() + */ + public static final String DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME = + "spring.placeholder.escapeCharacter.default"; + + /** + * Since {@code null} is a valid value for {@link #defaultEscapeCharacter}, + * this constant provides a way to represent an undefined (or not yet set) + * value. Consequently, {@link #getDefaultEscapeCharacter()} prevents the use + * of {@link Character#MIN_VALUE} as the actual escape character. + * @since 6.2.7 + */ + static final Character UNDEFINED_ESCAPE_CHARACTER = Character.MIN_VALUE; + + + /** + * Cached value for the default escape character. + * @since 6.2.7 + */ + @Nullable + static volatile Character defaultEscapeCharacter = UNDEFINED_ESCAPE_CHARACTER; + + protected final Log logger = LogFactory.getLog(getClass()); @Nullable @@ -62,7 +105,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR; @Nullable - private Character escapeCharacter = SystemPropertyUtils.ESCAPE_CHARACTER; + private Character escapeCharacter = getDefaultEscapeCharacter(); private final Set requiredProperties = new LinkedHashSet<>(); @@ -91,9 +134,9 @@ public void setConversionService(ConfigurableConversionService conversionService } /** - * Set the prefix that placeholders replaced by this resolver must begin with. - *

The default is "${". - * @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_PREFIX + * {@inheritDoc} + *

The default is "${". + * @see SystemPropertyUtils#PLACEHOLDER_PREFIX */ @Override public void setPlaceholderPrefix(String placeholderPrefix) { @@ -102,9 +145,9 @@ public void setPlaceholderPrefix(String placeholderPrefix) { } /** - * Set the suffix that placeholders replaced by this resolver must end with. - *

The default is "}". - * @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_SUFFIX + * {@inheritDoc} + *

The default is "}". + * @see SystemPropertyUtils#PLACEHOLDER_SUFFIX */ @Override public void setPlaceholderSuffix(String placeholderSuffix) { @@ -113,11 +156,9 @@ public void setPlaceholderSuffix(String placeholderSuffix) { } /** - * Specify the separating character between the placeholders replaced by this - * resolver and their associated default value, or {@code null} if no such - * special character should be processed as a value separator. - *

The default is ":". - * @see org.springframework.util.SystemPropertyUtils#VALUE_SEPARATOR + * {@inheritDoc} + *

The default is {@code ":"}. + * @see SystemPropertyUtils#VALUE_SEPARATOR */ @Override public void setValueSeparator(@Nullable String valueSeparator) { @@ -125,12 +166,9 @@ public void setValueSeparator(@Nullable String valueSeparator) { } /** - * Specify the escape character to use to ignore placeholder prefix - * or value separator, or {@code null} if no escaping should take - * place. - *

The default is "\". + * {@inheritDoc} + *

The default is determined by {@link #getDefaultEscapeCharacter()}. * @since 6.2 - * @see org.springframework.util.SystemPropertyUtils#ESCAPE_CHARACTER */ @Override public void setEscapeCharacter(@Nullable Character escapeCharacter) { @@ -291,4 +329,60 @@ protected T convertValueIfNecessary(Object value, @Nullable Class targetT @Nullable protected abstract String getPropertyAsRawString(String key); + + /** + * Get the default {@linkplain #setEscapeCharacter(Character) escape character} + * to use when parsing strings for property placeholder resolution. + *

This method attempts to retrieve the default escape character configured + * via the {@value #DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME} JVM system + * property or Spring property. + *

Falls back to {@code '\'} if the property has not been set. + * @return the configured default escape character, {@code null} if escape character + * support has been disabled, or {@code '\'} if the property has not been set + * @throws IllegalArgumentException if the property is configured with an + * invalid value, such as {@link Character#MIN_VALUE} or a string containing + * more than one character + * @since 6.2.7 + * @see #DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME + * @see SystemPropertyUtils#ESCAPE_CHARACTER + * @see SpringProperties + */ + @Nullable + public static Character getDefaultEscapeCharacter() throws IllegalArgumentException { + Character escapeCharacter = defaultEscapeCharacter; + if (UNDEFINED_ESCAPE_CHARACTER.equals(escapeCharacter)) { + String value = SpringProperties.getProperty(DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME); + if (value != null) { + if (value.isEmpty()) { + // Disable escape character support by default. + escapeCharacter = null; + } + else if (value.length() == 1) { + try { + // Use custom default escape character. + escapeCharacter = value.charAt(0); + } + catch (Exception ex) { + throw new IllegalArgumentException("Failed to process value [%s] for property [%s]: %s" + .formatted(value, DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME, ex.getMessage()), ex); + } + Assert.isTrue(!escapeCharacter.equals(Character.MIN_VALUE), + () -> "Value for property [%s] must not be Character.MIN_VALUE" + .formatted(DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME)); + } + else { + throw new IllegalArgumentException( + "Value [%s] for property [%s] must be a single character or an empty string" + .formatted(value, DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME)); + } + } + else { + // Use standard default value for the escape character. + escapeCharacter = SystemPropertyUtils.ESCAPE_CHARACTER; + } + defaultEscapeCharacter = escapeCharacter; + } + return escapeCharacter; + } + } diff --git a/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java index 5fe8115842c1..56f47247c926 100644 --- a/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -34,7 +34,10 @@ * *

As of Spring 4.1.2, this class extends {@link EnumerablePropertySource} instead * of plain {@link PropertySource}, exposing {@link #getPropertyNames()} based on the - * accumulated property names from all contained sources (as far as possible). + * accumulated property names from all contained sources - and failing with an + * {@code IllegalStateException} against any non-{@code EnumerablePropertySource}. + * When used through the {@code EnumerablePropertySource} contract, all contained + * sources are expected to be of type {@code EnumerablePropertySource} as well. * * @author Chris Beams * @author Juergen Hoeller diff --git a/spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java b/spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java index 9de866854fd0..34ecf6623400 100644 --- a/spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java +++ b/spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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. @@ -137,13 +137,13 @@ public interface ConfigurableEnvironment extends Environment, ConfigurableProper Map getSystemEnvironment(); /** - * Append the given parent environment's active profiles, default profiles and + * Append the given parent environment's active profiles, default profiles, and * property sources to this (child) environment's respective collections of each. *

For any identically-named {@code PropertySource} instance existing in both * parent and child, the child instance is to be preserved and the parent instance * discarded. This has the effect of allowing overriding of property sources by the - * child as well as avoiding redundant searches through common property source types, - * for example, system environment and system properties. + * child as well as avoiding redundant searches through common property source types + * — for example, system environment and system properties. *

Active and default profile names are also filtered for duplicates, to avoid * confusion and redundant storage. *

The parent environment remains unmodified in any case. Note that any changes to diff --git a/spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java index 01f47dae1f62..d440b78135b1 100644 --- a/spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java +++ b/spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -69,21 +69,23 @@ public interface ConfigurablePropertyResolver extends PropertyResolver { void setPlaceholderSuffix(String placeholderSuffix); /** - * Specify the separating character between the placeholders replaced by this - * resolver and their associated default value, or {@code null} if no such + * Set the separating character to be honored between placeholders replaced by + * this resolver and their associated default values, or {@code null} if no such * special character should be processed as a value separator. */ void setValueSeparator(@Nullable String valueSeparator); /** - * Specify the escape character to use to ignore placeholder prefix or - * value separator, or {@code null} if no escaping should take place. + * Set the escape character to use to ignore the + * {@linkplain #setPlaceholderPrefix(String) placeholder prefix} and the + * {@linkplain #setValueSeparator(String) value separator}, or {@code null} + * if no escaping should take place. * @since 6.2 */ void setEscapeCharacter(@Nullable Character escapeCharacter); /** - * Set whether to throw an exception when encountering an unresolvable placeholder + * Specify whether to throw an exception when encountering an unresolvable placeholder * nested within the value of a given property. A {@code false} value indicates strict * resolution, i.e. that an exception will be thrown. A {@code true} value indicates * that unresolvable nested placeholders should be passed through in their unresolved @@ -106,7 +108,7 @@ public interface ConfigurablePropertyResolver extends PropertyResolver { * {@link #setRequiredProperties} is present and resolves to a * non-{@code null} value. * @throws MissingRequiredPropertiesException if any of the required - * properties are not resolvable. + * properties are not resolvable */ void validateRequiredProperties() throws MissingRequiredPropertiesException; diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java index 173a1a33784d..55a7e84968d5 100644 --- a/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java +++ b/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 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. @@ -30,13 +30,13 @@ public interface PropertyResolver { /** - * Return whether the given property key is available for resolution, - * i.e. if the value for the given key is not {@code null}. + * Determine whether the given property key is available for resolution + * — for example, if the value for the given key is not {@code null}. */ boolean containsProperty(String key); /** - * Return the property value associated with the given key, + * Resolve the property value associated with the given key, * or {@code null} if the key cannot be resolved. * @param key the property name to resolve * @see #getProperty(String, String) @@ -47,7 +47,7 @@ public interface PropertyResolver { String getProperty(String key); /** - * Return the property value associated with the given key, or + * Resolve the property value associated with the given key, or * {@code defaultValue} if the key cannot be resolved. * @param key the property name to resolve * @param defaultValue the default value to return if no value is found @@ -57,7 +57,7 @@ public interface PropertyResolver { String getProperty(String key, String defaultValue); /** - * Return the property value associated with the given key, + * Resolve the property value associated with the given key, * or {@code null} if the key cannot be resolved. * @param key the property name to resolve * @param targetType the expected type of the property value @@ -67,7 +67,7 @@ public interface PropertyResolver { T getProperty(String key, Class targetType); /** - * Return the property value associated with the given key, + * Resolve the property value associated with the given key, * or {@code defaultValue} if the key cannot be resolved. * @param key the property name to resolve * @param targetType the expected type of the property value @@ -77,14 +77,14 @@ public interface PropertyResolver { T getProperty(String key, Class targetType, T defaultValue); /** - * Return the property value associated with the given key (never {@code null}). + * Resolve the property value associated with the given key (never {@code null}). * @throws IllegalStateException if the key cannot be resolved * @see #getRequiredProperty(String, Class) */ String getRequiredProperty(String key) throws IllegalStateException; /** - * Return the property value associated with the given key, converted to the given + * Resolve the property value associated with the given key, converted to the given * targetType (never {@code null}). * @throws IllegalStateException if the given key cannot be resolved */ diff --git a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java index 2dcfb4f322dc..13d4f2cddb2a 100644 --- a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java @@ -29,6 +29,7 @@ import java.nio.file.NoSuchFileException; import java.nio.file.StandardOpenOption; import java.util.jar.JarEntry; +import java.util.jar.JarFile; import org.springframework.util.ResourceUtils; @@ -44,6 +45,7 @@ */ public abstract class AbstractFileResolvingResource extends AbstractResource { + @SuppressWarnings("try") @Override public boolean exists() { try { @@ -86,7 +88,11 @@ else if (code == HttpURLConnection.HTTP_NOT_FOUND) { if (con instanceof JarURLConnection jarCon) { // For JarURLConnection, do not check content-length but rather the // existence of the entry (or the jar root in case of no entryName). - return (jarCon.getEntryName() == null || jarCon.getJarEntry() != null); + // getJarFile() called for enforced presence check of the jar file, + // throwing a NoSuchFileException otherwise (turned to false below). + try (JarFile jarFile = jarCon.getJarFile()) { + return (jarCon.getEntryName() == null || jarCon.getJarEntry() != null); + } } else if (con.getContentLengthLong() > 0) { return true; diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java index 7fe7c54b082f..4a73678fd86d 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java @@ -36,6 +36,7 @@ import java.nio.file.FileSystems; import java.nio.file.FileVisitOption; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.Collections; import java.util.Enumeration; @@ -874,9 +875,9 @@ protected Set doFindPathMatchingJarResources(Resource rootDirResource, rootEntryPath = (jarEntry != null ? jarEntry.getName() : ""); closeJarFile = !jarCon.getUseCaches(); } - catch (ZipException | FileNotFoundException ex) { + catch (ZipException | FileNotFoundException | NoSuchFileException ex) { // Happens in case of a non-jar file or in case of a cached root directory - // without specific subdirectory present, respectively. + // without the specific subdirectory present, respectively. return Collections.emptySet(); } } @@ -1275,7 +1276,7 @@ private static String fixPath(String path) { } /** - * Return a alternative form of the resource, i.e. with or without a leading slash. + * Return an alternative form of the resource, i.e. with or without a leading slash. * @param path the file path (with or without a leading slash) * @return the alternative form or {@code null} */ diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java index 85d53d40475a..12cf39559cae 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -141,8 +141,8 @@ public void setPropertiesPersister(@Nullable PropertiesPersister propertiesPersi /** - * Return a merged Properties instance containing both the - * loaded properties and properties set on this FactoryBean. + * Return a merged {@link Properties} instance containing both the + * loaded properties and properties set on this component. */ protected Properties mergeProperties() throws IOException { Properties result = new Properties(); diff --git a/spring-core/src/main/java/org/springframework/util/PatternMatchUtils.java b/spring-core/src/main/java/org/springframework/util/PatternMatchUtils.java index 9f050351f0b6..f0f0070567d0 100644 --- a/spring-core/src/main/java/org/springframework/util/PatternMatchUtils.java +++ b/spring-core/src/main/java/org/springframework/util/PatternMatchUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 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. @@ -37,13 +37,25 @@ public abstract class PatternMatchUtils { * @return whether the String matches the given pattern */ public static boolean simpleMatch(@Nullable String pattern, @Nullable String str) { + return simpleMatch(pattern, str, false); + } + + /** + * Variant of {@link #simpleMatch(String, String)} that ignores upper/lower case. + * @since 6.1.20 + */ + public static boolean simpleMatchIgnoreCase(@Nullable String pattern, @Nullable String str) { + return simpleMatch(pattern, str, true); + } + + private static boolean simpleMatch(@Nullable String pattern, @Nullable String str, boolean ignoreCase) { if (pattern == null || str == null) { return false; } int firstIndex = pattern.indexOf('*'); if (firstIndex == -1) { - return pattern.equals(str); + return (ignoreCase ? pattern.equalsIgnoreCase(str) : pattern.equals(str)); } if (firstIndex == 0) { @@ -52,25 +64,43 @@ public static boolean simpleMatch(@Nullable String pattern, @Nullable String str } int nextIndex = pattern.indexOf('*', 1); if (nextIndex == -1) { - return str.endsWith(pattern.substring(1)); + String part = pattern.substring(1); + return (ignoreCase ? StringUtils.endsWithIgnoreCase(str, part) : str.endsWith(part)); } String part = pattern.substring(1, nextIndex); if (part.isEmpty()) { - return simpleMatch(pattern.substring(nextIndex), str); + return simpleMatch(pattern.substring(nextIndex), str, ignoreCase); } - int partIndex = str.indexOf(part); + int partIndex = indexOf(str, part, 0, ignoreCase); while (partIndex != -1) { - if (simpleMatch(pattern.substring(nextIndex), str.substring(partIndex + part.length()))) { + if (simpleMatch(pattern.substring(nextIndex), str.substring(partIndex + part.length()), ignoreCase)) { return true; } - partIndex = str.indexOf(part, partIndex + 1); + partIndex = indexOf(str, part, partIndex + 1, ignoreCase); } return false; } return (str.length() >= firstIndex && - pattern.startsWith(str.substring(0, firstIndex)) && - simpleMatch(pattern.substring(firstIndex), str.substring(firstIndex))); + checkStartsWith(pattern, str, firstIndex, ignoreCase) && + simpleMatch(pattern.substring(firstIndex), str.substring(firstIndex), ignoreCase)); + } + + private static boolean checkStartsWith(String pattern, String str, int index, boolean ignoreCase) { + String part = str.substring(0, index); + return (ignoreCase ? StringUtils.startsWithIgnoreCase(pattern, part) : pattern.startsWith(part)); + } + + private static int indexOf(String str, String otherStr, int startIndex, boolean ignoreCase) { + if (!ignoreCase) { + return str.indexOf(otherStr, startIndex); + } + for (int i = startIndex; i <= (str.length() - otherStr.length()); i++) { + if (str.regionMatches(true, i, otherStr, 0, otherStr.length())) { + return i; + } + } + return -1; } /** @@ -94,4 +124,19 @@ public static boolean simpleMatch(@Nullable String[] patterns, @Nullable String return false; } + /** + * Variant of {@link #simpleMatch(String[], String)} that ignores upper/lower case. + * @since 6.1.20 + */ + public static boolean simpleMatchIgnoreCase(@Nullable String[] patterns, @Nullable String str) { + if (patterns != null) { + for (String pattern : patterns) { + if (simpleMatch(pattern, str, true)) { + return true; + } + } + } + return false; + } + } diff --git a/spring-core/src/main/java/org/springframework/util/StreamUtils.java b/spring-core/src/main/java/org/springframework/util/StreamUtils.java index 99b44e03f29e..354e7b2cf20f 100644 --- a/spring-core/src/main/java/org/springframework/util/StreamUtils.java +++ b/spring-core/src/main/java/org/springframework/util/StreamUtils.java @@ -239,7 +239,7 @@ public static OutputStream nonClosing(OutputStream out) { } - private static class NonClosingInputStream extends FilterInputStream { + private static final class NonClosingInputStream extends FilterInputStream { public NonClosingInputStream(InputStream in) { super(in); @@ -248,10 +248,30 @@ public NonClosingInputStream(InputStream in) { @Override public void close() throws IOException { } + + @Override + public byte[] readAllBytes() throws IOException { + return in.readAllBytes(); + } + + @Override + public byte[] readNBytes(int len) throws IOException { + return in.readNBytes(len); + } + + @Override + public int readNBytes(byte[] b, int off, int len) throws IOException { + return in.readNBytes(b, off, len); + } + + @Override + public long transferTo(OutputStream out) throws IOException { + return in.transferTo(out); + } } - private static class NonClosingOutputStream extends FilterOutputStream { + private static final class NonClosingOutputStream extends FilterOutputStream { public NonClosingOutputStream(OutputStream out) { super(out); diff --git a/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java b/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java index eb962efc5bb3..fae3b69934b4 100644 --- a/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java +++ b/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -35,16 +35,19 @@ */ public abstract class SystemPropertyUtils { - /** Prefix for system property placeholders: {@value}. */ + /** Prefix for property placeholders: {@value}. */ public static final String PLACEHOLDER_PREFIX = "${"; - /** Suffix for system property placeholders: {@value}. */ + /** Suffix for property placeholders: {@value}. */ public static final String PLACEHOLDER_SUFFIX = "}"; - /** Value separator for system property placeholders: {@value}. */ + /** Value separator for property placeholders: {@value}. */ public static final String VALUE_SEPARATOR = ":"; - /** Default escape character: {@code '\'}. */ + /** + * Escape character for property placeholders: {@code '\'}. + * @since 6.2 + */ public static final Character ESCAPE_CHARACTER = '\\'; diff --git a/spring-core/src/main/java/org/springframework/util/backoff/ExponentialBackOff.java b/spring-core/src/main/java/org/springframework/util/backoff/ExponentialBackOff.java index 5cd39685fa28..79836518a165 100644 --- a/spring-core/src/main/java/org/springframework/util/backoff/ExponentialBackOff.java +++ b/spring-core/src/main/java/org/springframework/util/backoff/ExponentialBackOff.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -85,6 +85,7 @@ public class ExponentialBackOff implements BackOff { */ public static final int DEFAULT_MAX_ATTEMPTS = Integer.MAX_VALUE; + private long initialInterval = DEFAULT_INITIAL_INTERVAL; private double multiplier = DEFAULT_MULTIPLIER; @@ -204,6 +205,7 @@ public int getMaxAttempts() { return this.maxAttempts; } + @Override public BackOffExecution start() { return new ExponentialBackOffExecution(); @@ -225,6 +227,7 @@ public String toString() { .toString(); } + private class ExponentialBackOffExecution implements BackOffExecution { private long currentInterval = -1; diff --git a/spring-core/src/main/java/org/springframework/util/backoff/FixedBackOff.java b/spring-core/src/main/java/org/springframework/util/backoff/FixedBackOff.java index b4d80c481227..9695077362b1 100644 --- a/spring-core/src/main/java/org/springframework/util/backoff/FixedBackOff.java +++ b/spring-core/src/main/java/org/springframework/util/backoff/FixedBackOff.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2025 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. @@ -35,6 +35,7 @@ public class FixedBackOff implements BackOff { */ public static final long UNLIMITED_ATTEMPTS = Long.MAX_VALUE; + private long interval = DEFAULT_INTERVAL; private long maxAttempts = UNLIMITED_ATTEMPTS; @@ -86,6 +87,7 @@ public long getMaxAttempts() { return this.maxAttempts; } + @Override public BackOffExecution start() { return new FixedBackOffExecution(); diff --git a/spring-core/src/test/java/org/springframework/core/env/AbstractPropertyResolverTests.java b/spring-core/src/test/java/org/springframework/core/env/AbstractPropertyResolverTests.java new file mode 100644 index 000000000000..ca536d83078c --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/env/AbstractPropertyResolverTests.java @@ -0,0 +1,120 @@ +/* + * Copyright 2002-2025 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.core.env; + +import java.util.stream.IntStream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.core.SpringProperties; +import org.springframework.lang.Nullable; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.springframework.core.env.AbstractPropertyResolver.DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME; +import static org.springframework.core.env.AbstractPropertyResolver.UNDEFINED_ESCAPE_CHARACTER; + +/** + * Unit tests for {@link AbstractPropertyResolver}. + * + * @author Sam Brannen + * @since 6.2.7 + */ +class AbstractPropertyResolverTests { + + @BeforeEach + void resetStateBeforeEachTest() { + resetState(); + } + + @AfterAll + static void resetState() { + AbstractPropertyResolver.defaultEscapeCharacter = UNDEFINED_ESCAPE_CHARACTER; + setSpringProperty(null); + } + + + @Test + void getDefaultEscapeCharacterWithSpringPropertySetToCharacterMinValue() { + setSpringProperty("" + Character.MIN_VALUE); + + assertThatIllegalArgumentException() + .isThrownBy(AbstractPropertyResolver::getDefaultEscapeCharacter) + .withMessage("Value for property [%s] must not be Character.MIN_VALUE", + DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME); + + assertThat(AbstractPropertyResolver.defaultEscapeCharacter).isEqualTo(UNDEFINED_ESCAPE_CHARACTER); + } + + @Test + void getDefaultEscapeCharacterWithSpringPropertySetToXyz() { + setSpringProperty("XYZ"); + + assertThatIllegalArgumentException() + .isThrownBy(AbstractPropertyResolver::getDefaultEscapeCharacter) + .withMessage("Value [XYZ] for property [%s] must be a single character or an empty string", + DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME); + + assertThat(AbstractPropertyResolver.defaultEscapeCharacter).isEqualTo(UNDEFINED_ESCAPE_CHARACTER); + } + + @Test + void getDefaultEscapeCharacterWithSpringPropertySetToEmptyString() { + setSpringProperty(""); + assertEscapeCharacter(null); + } + + @Test + void getDefaultEscapeCharacterWithoutSpringPropertySet() { + assertEscapeCharacter('\\'); + } + + @Test + void getDefaultEscapeCharacterWithSpringPropertySetToBackslash() { + setSpringProperty("\\"); + assertEscapeCharacter('\\'); + } + + @Test + void getDefaultEscapeCharacterWithSpringPropertySetToTilde() { + setSpringProperty("~"); + assertEscapeCharacter('~'); + } + + @Test + void getDefaultEscapeCharacterFromMultipleThreads() { + setSpringProperty("~"); + + IntStream.range(1, 32).parallel().forEach(__ -> + assertThat(AbstractPropertyResolver.getDefaultEscapeCharacter()).isEqualTo('~')); + + assertThat(AbstractPropertyResolver.defaultEscapeCharacter).isEqualTo('~'); + } + + + private static void setSpringProperty(String value) { + SpringProperties.setProperty(DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME, value); + } + + private static void assertEscapeCharacter(@Nullable Character expected) { + assertThat(AbstractPropertyResolver.getDefaultEscapeCharacter()).isEqualTo(expected); + assertThat(AbstractPropertyResolver.defaultEscapeCharacter).isEqualTo(expected); + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java b/spring-core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java index 23654dfe10e5..514052e47c6b 100644 --- a/spring-core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -21,6 +21,7 @@ import java.util.Properties; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.core.convert.ConverterNotFoundException; @@ -38,18 +39,15 @@ */ class PropertySourcesPropertyResolverTests { - private Properties testProperties; + private final Properties testProperties = new Properties(); - private MutablePropertySources propertySources; + private final MutablePropertySources propertySources = new MutablePropertySources(); - private ConfigurablePropertyResolver propertyResolver; + private final PropertySourcesPropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources); @BeforeEach void setUp() { - propertySources = new MutablePropertySources(); - propertyResolver = new PropertySourcesPropertyResolver(propertySources); - testProperties = new Properties(); propertySources.addFirst(new PropertiesPropertySource("testProperties", testProperties)); } @@ -77,14 +75,12 @@ void getProperty_withDefaultValue() { @Test void getProperty_propertySourceSearchOrderIsFIFO() { - MutablePropertySources sources = new MutablePropertySources(); - PropertyResolver resolver = new PropertySourcesPropertyResolver(sources); - sources.addFirst(new MockPropertySource("ps1").withProperty("pName", "ps1Value")); - assertThat(resolver.getProperty("pName")).isEqualTo("ps1Value"); - sources.addFirst(new MockPropertySource("ps2").withProperty("pName", "ps2Value")); - assertThat(resolver.getProperty("pName")).isEqualTo("ps2Value"); - sources.addFirst(new MockPropertySource("ps3").withProperty("pName", "ps3Value")); - assertThat(resolver.getProperty("pName")).isEqualTo("ps3Value"); + propertySources.addFirst(new MockPropertySource("ps1").withProperty("pName", "ps1Value")); + assertThat(propertyResolver.getProperty("pName")).isEqualTo("ps1Value"); + propertySources.addFirst(new MockPropertySource("ps2").withProperty("pName", "ps2Value")); + assertThat(propertyResolver.getProperty("pName")).isEqualTo("ps2Value"); + propertySources.addFirst(new MockPropertySource("ps3").withProperty("pName", "ps3Value")); + assertThat(propertyResolver.getProperty("pName")).isEqualTo("ps3Value"); } @Test @@ -115,8 +111,8 @@ void getProperty_withNonConvertibleTargetType() { class TestType { } - assertThatExceptionOfType(ConverterNotFoundException.class).isThrownBy(() -> - propertyResolver.getProperty("foo", TestType.class)); + assertThatExceptionOfType(ConverterNotFoundException.class) + .isThrownBy(() -> propertyResolver.getProperty("foo", TestType.class)); } @Test @@ -127,7 +123,6 @@ void getProperty_doesNotCache_replaceExistingKeyPostConstruction() { HashMap map = new HashMap<>(); map.put(key, value1); // before construction - MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MapPropertySource("testProperties", map)); PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources); assertThat(propertyResolver.getProperty(key)).isEqualTo(value1); @@ -138,7 +133,6 @@ void getProperty_doesNotCache_replaceExistingKeyPostConstruction() { @Test void getProperty_doesNotCache_addNewKeyPostConstruction() { HashMap map = new HashMap<>(); - MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MapPropertySource("testProperties", map)); PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources); assertThat(propertyResolver.getProperty("foo")).isNull(); @@ -148,10 +142,9 @@ void getProperty_doesNotCache_addNewKeyPostConstruction() { @Test void getPropertySources_replacePropertySource() { - propertySources = new MutablePropertySources(); - propertyResolver = new PropertySourcesPropertyResolver(propertySources); propertySources.addLast(new MockPropertySource("local").withProperty("foo", "localValue")); propertySources.addLast(new MockPropertySource("system").withProperty("foo", "systemValue")); + assertThat(propertySources).hasSize(3); // 'local' was added first so has precedence assertThat(propertyResolver.getProperty("foo")).isEqualTo("localValue"); @@ -162,7 +155,7 @@ void getPropertySources_replacePropertySource() { // 'system' now has precedence assertThat(propertyResolver.getProperty("foo")).isEqualTo("newValue"); - assertThat(propertySources).hasSize(2); + assertThat(propertySources).hasSize(3); } @Test @@ -170,81 +163,65 @@ void getRequiredProperty() { testProperties.put("exists", "xyz"); assertThat(propertyResolver.getRequiredProperty("exists")).isEqualTo("xyz"); - assertThatIllegalStateException().isThrownBy(() -> - propertyResolver.getRequiredProperty("bogus")); + assertThatIllegalStateException().isThrownBy(() -> propertyResolver.getRequiredProperty("bogus")); } @Test void getRequiredProperty_withStringArrayConversion() { testProperties.put("exists", "abc,123"); - assertThat(propertyResolver.getRequiredProperty("exists", String[].class)).isEqualTo(new String[] { "abc", "123" }); + assertThat(propertyResolver.getRequiredProperty("exists", String[].class)).containsExactly("abc", "123"); - assertThatIllegalStateException().isThrownBy(() -> - propertyResolver.getRequiredProperty("bogus", String[].class)); + assertThatIllegalStateException().isThrownBy(() -> propertyResolver.getRequiredProperty("bogus", String[].class)); } @Test void resolvePlaceholders() { - MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("key", "value")); - PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); - assertThat(resolver.resolvePlaceholders("Replace this ${key}")).isEqualTo("Replace this value"); + assertThat(propertyResolver.resolvePlaceholders("Replace this ${key}")).isEqualTo("Replace this value"); } @Test void resolvePlaceholders_withUnresolvable() { - MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("key", "value")); - PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); - assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown}")) + assertThat(propertyResolver.resolvePlaceholders("Replace this ${key} plus ${unknown}")) .isEqualTo("Replace this value plus ${unknown}"); } @Test void resolvePlaceholders_withDefaultValue() { - MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("key", "value")); - PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); - assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown:defaultValue}")) + assertThat(propertyResolver.resolvePlaceholders("Replace this ${key} plus ${unknown:defaultValue}")) .isEqualTo("Replace this value plus defaultValue"); } @Test void resolvePlaceholders_withNullInput() { - assertThatIllegalArgumentException().isThrownBy(() -> - new PropertySourcesPropertyResolver(new MutablePropertySources()).resolvePlaceholders(null)); + assertThatIllegalArgumentException().isThrownBy(() -> propertyResolver.resolvePlaceholders(null)); } @Test void resolveRequiredPlaceholders() { - MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("key", "value")); - PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); - assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key}")).isEqualTo("Replace this value"); + assertThat(propertyResolver.resolveRequiredPlaceholders("Replace this ${key}")).isEqualTo("Replace this value"); } @Test void resolveRequiredPlaceholders_withUnresolvable() { - MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("key", "value")); - PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); - assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() -> - resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown}")); + assertThatExceptionOfType(PlaceholderResolutionException.class) + .isThrownBy(() -> propertyResolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown}")); } @Test void resolveRequiredPlaceholders_withDefaultValue() { - MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("key", "value")); - PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); - assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown:defaultValue}")) + assertThat(propertyResolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown:defaultValue}")) .isEqualTo("Replace this value plus defaultValue"); } @Test void resolveRequiredPlaceholders_withNullInput() { - assertThatIllegalArgumentException().isThrownBy(() -> - new PropertySourcesPropertyResolver(new MutablePropertySources()).resolveRequiredPlaceholders(null)); + assertThatIllegalArgumentException().isThrownBy(() -> propertyResolver.resolveRequiredPlaceholders(null)); } @Test @@ -256,17 +233,17 @@ void setRequiredProperties_andValidateRequiredProperties() { propertyResolver.setRequiredProperties("foo", "bar"); // neither foo nor bar properties are present -> validating should throw - assertThatExceptionOfType(MissingRequiredPropertiesException.class).isThrownBy( - propertyResolver::validateRequiredProperties) - .withMessage("The following properties were declared as required " + - "but could not be resolved: [foo, bar]"); + assertThatExceptionOfType(MissingRequiredPropertiesException.class) + .isThrownBy(propertyResolver::validateRequiredProperties) + .withMessage("The following properties were declared as required " + + "but could not be resolved: [foo, bar]"); // add foo property -> validation should fail only on missing 'bar' property testProperties.put("foo", "fooValue"); - assertThatExceptionOfType(MissingRequiredPropertiesException.class).isThrownBy( - propertyResolver::validateRequiredProperties) - .withMessage("The following properties were declared as required " + - "but could not be resolved: [bar]"); + assertThatExceptionOfType(MissingRequiredPropertiesException.class) + .isThrownBy(propertyResolver::validateRequiredProperties) + .withMessage("The following properties were declared as required " + + "but could not be resolved: [bar]"); // add bar property -> validation should pass, even with an empty string value testProperties.put("bar", ""); @@ -291,13 +268,13 @@ void resolveNestedPropertyPlaceholders() { assertThat(pr.getProperty("p2")).isEqualTo("v2"); assertThat(pr.getProperty("p3")).isEqualTo("v1:v2"); assertThat(pr.getProperty("p4")).isEqualTo("v1:v2"); - assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() -> - pr.getProperty("p5")) - .withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\""); + assertThatExceptionOfType(PlaceholderResolutionException.class) + .isThrownBy(() -> pr.getProperty("p5")) + .withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\""); assertThat(pr.getProperty("p6")).isEqualTo("v1:v2:def"); - assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() -> - pr.getProperty("pL")) - .withMessageContaining("Circular"); + assertThatExceptionOfType(PlaceholderResolutionException.class) + .isThrownBy(() -> pr.getProperty("pL")) + .withMessageContaining("Circular"); } @Test @@ -349,9 +326,9 @@ void ignoreUnresolvableNestedPlaceholdersIsConfigurable() { // placeholders nested within the value of "p4" are unresolvable and cause an // exception by default - assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() -> - pr.getProperty("p4")) - .withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\""); + assertThatExceptionOfType(PlaceholderResolutionException.class) + .isThrownBy(() -> pr.getProperty("p4")) + .withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\""); // relax the treatment of unresolvable nested placeholders pr.setIgnoreUnresolvableNestedPlaceholders(true); @@ -361,9 +338,58 @@ void ignoreUnresolvableNestedPlaceholdersIsConfigurable() { // resolve[Nested]Placeholders methods behave as usual regardless the value of // ignoreUnresolvableNestedPlaceholders assertThat(pr.resolvePlaceholders("${p1}:${p2}:${bogus}")).isEqualTo("v1:v2:${bogus}"); - assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() -> - pr.resolveRequiredPlaceholders("${p1}:${p2}:${bogus}")) - .withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\""); + assertThatExceptionOfType(PlaceholderResolutionException.class) + .isThrownBy(() -> pr.resolveRequiredPlaceholders("${p1}:${p2}:${bogus}")) + .withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\""); + } + + + @Nested + class EscapedPlaceholderTests { + + @Test // gh-34720 + void escapedPlaceholdersAreNotEvaluated() { + testProperties.put("prop1", "value1"); + testProperties.put("prop2", "value2\\${prop1}"); + + assertThat(propertyResolver.getProperty("prop2")).isEqualTo("value2${prop1}"); + } + + @Test // gh-34720 + void escapedPlaceholdersAreNotEvaluatedWithCharSequenceValues() { + testProperties.put("prop1", "value1"); + testProperties.put("prop2", new StringBuilder("value2\\${prop1}")); + + assertThat(propertyResolver.getProperty("prop2")).isEqualTo("value2${prop1}"); + } + + @Test // gh-34720 + void multipleEscapedPlaceholdersArePreserved() { + testProperties.put("prop1", "value1"); + testProperties.put("prop2", "value2"); + testProperties.put("complex", "start\\${prop1}middle\\${prop2}end"); + + assertThat(propertyResolver.getProperty("complex")).isEqualTo("start${prop1}middle${prop2}end"); + } + + @Test // gh-34720 + void doubleBackslashesAreProcessedCorrectly() { + testProperties.put("prop1", "value1"); + testProperties.put("doubleEscaped", "value2\\\\${prop1}"); + + assertThat(propertyResolver.getProperty("doubleEscaped")).isEqualTo("value2\\${prop1}"); + } + + @Test // gh-34720 + void escapedPlaceholdersInNestedPropertiesAreNotEvaluated() { + testProperties.put("p1", "v1"); + testProperties.put("p2", "v2"); + testProperties.put("escaped", "prefix-\\${p1}"); + testProperties.put("nested", "${escaped}-${p2}"); + + assertThat(propertyResolver.getProperty("nested")).isEqualTo("prefix-${p1}-v2"); + } + } } diff --git a/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java b/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java index 780fa2331699..5ce4c7764e78 100644 --- a/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java @@ -335,6 +335,11 @@ private void writeAssetJar(Path path) throws Exception { } assertThat(new FileSystemResource(path).exists()).isTrue(); assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue(); + assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/file.txt").exists()).isTrue(); + assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/none.txt").exists()).isFalse(); + assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + "X" + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isFalse(); + assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + "X" + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/file.txt").exists()).isFalse(); + assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + "X" + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/none.txt").exists()).isFalse(); } private void writeApplicationJar(Path path) throws Exception { diff --git a/spring-core/src/test/java/org/springframework/util/PatternMatchUtilsTests.java b/spring-core/src/test/java/org/springframework/util/PatternMatchUtilsTests.java index b4618c090d78..d2ef171a30f5 100644 --- a/spring-core/src/test/java/org/springframework/util/PatternMatchUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/PatternMatchUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -53,18 +53,22 @@ void trivial() { assertMatches(new String[] { null, "" }, ""); assertMatches(new String[] { null, "123" }, "123"); assertMatches(new String[] { null, "*" }, "123"); + + testMixedCaseMatch("abC", "Abc"); } @Test void startsWith() { assertMatches("get*", "getMe"); assertDoesNotMatch("get*", "setMe"); + testMixedCaseMatch("geT*", "GetMe"); } @Test void endsWith() { assertMatches("*Test", "getMeTest"); assertDoesNotMatch("*Test", "setMe"); + testMixedCaseMatch("*TeSt", "getMeTesT"); } @Test @@ -74,6 +78,10 @@ void between() { assertMatches("*stuff*", "stuffTest"); assertMatches("*stuff*", "getstuff"); assertMatches("*stuff*", "stuff"); + testMixedCaseMatch("*stuff*", "getStuffTest"); + testMixedCaseMatch("*stuff*", "StuffTest"); + testMixedCaseMatch("*stuff*", "getStuff"); + testMixedCaseMatch("*stuff*", "Stuff"); } @Test @@ -82,6 +90,8 @@ void startsEnds() { assertMatches("on*Event", "onEvent"); assertDoesNotMatch("3*3", "3"); assertMatches("3*3", "33"); + testMixedCaseMatch("on*Event", "OnMyEvenT"); + testMixedCaseMatch("on*Event", "OnEvenT"); } @Test @@ -122,18 +132,27 @@ void patternVariants() { private void assertMatches(String pattern, String str) { assertThat(PatternMatchUtils.simpleMatch(pattern, str)).isTrue(); + assertThat(PatternMatchUtils.simpleMatchIgnoreCase(pattern, str)).isTrue(); } private void assertDoesNotMatch(String pattern, String str) { assertThat(PatternMatchUtils.simpleMatch(pattern, str)).isFalse(); + assertThat(PatternMatchUtils.simpleMatchIgnoreCase(pattern, str)).isFalse(); + } + + private void testMixedCaseMatch(String pattern, String str) { + assertThat(PatternMatchUtils.simpleMatch(pattern, str)).isFalse(); + assertThat(PatternMatchUtils.simpleMatchIgnoreCase(pattern, str)).isTrue(); } private void assertMatches(String[] patterns, String str) { assertThat(PatternMatchUtils.simpleMatch(patterns, str)).isTrue(); + assertThat(PatternMatchUtils.simpleMatchIgnoreCase(patterns, str)).isTrue(); } private void assertDoesNotMatch(String[] patterns, String str) { assertThat(PatternMatchUtils.simpleMatch(patterns, str)).isFalse(); + assertThat(PatternMatchUtils.simpleMatchIgnoreCase(patterns, str)).isFalse(); } } diff --git a/spring-core/src/test/java/org/springframework/util/PlaceholderParserTests.java b/spring-core/src/test/java/org/springframework/util/PlaceholderParserTests.java index 53182acf882e..b16f741517aa 100644 --- a/spring-core/src/test/java/org/springframework/util/PlaceholderParserTests.java +++ b/spring-core/src/test/java/org/springframework/util/PlaceholderParserTests.java @@ -16,7 +16,7 @@ package org.springframework.util; -import java.util.Properties; +import java.util.Map; import java.util.stream.Stream; import org.junit.jupiter.api.Nested; @@ -36,13 +36,13 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; /** * Tests for {@link PlaceholderParser}. * * @author Stephane Nicoll + * @author Sam Brannen */ class PlaceholderParserTests { @@ -54,11 +54,11 @@ class OnlyPlaceholderTests { @ParameterizedTest(name = "{0} -> {1}") @MethodSource("placeholders") void placeholderIsReplaced(String text, String expected) { - Properties properties = new Properties(); - properties.setProperty("firstName", "John"); - properties.setProperty("nested0", "first"); - properties.setProperty("nested1", "Name"); - assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected); + Map properties = Map.of( + "firstName", "John", + "nested0", "first", + "nested1", "Name"); + assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected); } static Stream placeholders() { @@ -79,13 +79,13 @@ static Stream placeholders() { @ParameterizedTest(name = "{0} -> {1}") @MethodSource("nestedPlaceholders") void nestedPlaceholdersAreReplaced(String text, String expected) { - Properties properties = new Properties(); - properties.setProperty("p1", "v1"); - properties.setProperty("p2", "v2"); - properties.setProperty("p3", "${p1}:${p2}"); // nested placeholders - properties.setProperty("p4", "${p3}"); // deeply nested placeholders - properties.setProperty("p5", "${p1}:${p2}:${bogus}"); // unresolvable placeholder - assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected); + Map properties = Map.of( + "p1", "v1", + "p2", "v2", + "p3", "${p1}:${p2}", // nested placeholders + "p4", "${p3}", // deeply nested placeholders + "p5", "${p1}:${p2}:${bogus}"); // unresolvable placeholder + assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected); } static Stream nestedPlaceholders() { @@ -101,19 +101,15 @@ static Stream nestedPlaceholders() { @Test void parseWithSinglePlaceholder() { PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John"); - assertThat(this.parser.replacePlaceholders("${firstName}", resolver)) - .isEqualTo("John"); - verify(resolver).resolvePlaceholder("firstName"); - verifyNoMoreInteractions(resolver); + assertThat(this.parser.replacePlaceholders("${firstName}", resolver)).isEqualTo("John"); + verifyPlaceholderResolutions(resolver, "firstName"); } @Test void parseWithPlaceholderAndPrefixText() { PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John"); - assertThat(this.parser.replacePlaceholders("This is ${firstName}", resolver)) - .isEqualTo("This is John"); - verify(resolver).resolvePlaceholder("firstName"); - verifyNoMoreInteractions(resolver); + assertThat(this.parser.replacePlaceholders("This is ${firstName}", resolver)).isEqualTo("This is John"); + verifyPlaceholderResolutions(resolver, "firstName"); } @Test @@ -121,31 +117,25 @@ void parseWithMultiplePlaceholdersAndText() { PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John", "lastName", "Smith"); assertThat(this.parser.replacePlaceholders("User: ${firstName} - ${lastName}.", resolver)) .isEqualTo("User: John - Smith."); - verify(resolver).resolvePlaceholder("firstName"); - verify(resolver).resolvePlaceholder("lastName"); - verifyNoMoreInteractions(resolver); + verifyPlaceholderResolutions(resolver, "firstName", "lastName"); } @Test void parseWithNestedPlaceholderInKey() { - PlaceholderResolver resolver = mockPlaceholderResolver( - "nested", "Name", "firstName", "John"); - assertThat(this.parser.replacePlaceholders("${first${nested}}", resolver)) - .isEqualTo("John"); + PlaceholderResolver resolver = mockPlaceholderResolver("nested", "Name", "firstName", "John"); + assertThat(this.parser.replacePlaceholders("${first${nested}}", resolver)).isEqualTo("John"); verifyPlaceholderResolutions(resolver, "nested", "firstName"); } @Test void parseWithMultipleNestedPlaceholdersInKey() { - PlaceholderResolver resolver = mockPlaceholderResolver( - "nested0", "first", "nested1", "Name", "firstName", "John"); - assertThat(this.parser.replacePlaceholders("${${nested0}${nested1}}", resolver)) - .isEqualTo("John"); + PlaceholderResolver resolver = mockPlaceholderResolver("nested0", "first", "nested1", "Name", "firstName", "John"); + assertThat(this.parser.replacePlaceholders("${${nested0}${nested1}}", resolver)).isEqualTo("John"); verifyPlaceholderResolutions(resolver, "nested0", "nested1", "firstName"); } @Test - void placeholdersWithSeparatorAreHandledAsIs() { + void placeholderValueContainingSeparatorIsHandledAsIs() { PlaceholderResolver resolver = mockPlaceholderResolver("my:test", "value"); assertThat(this.parser.replacePlaceholders("${my:test}", resolver)).isEqualTo("value"); verifyPlaceholderResolutions(resolver, "my:test"); @@ -153,17 +143,20 @@ void placeholdersWithSeparatorAreHandledAsIs() { @Test void placeholdersWithoutEscapeCharAreNotEscaped() { - PlaceholderResolver resolver = mockPlaceholderResolver("test", "value"); - assertThat(this.parser.replacePlaceholders("\\${test}", resolver)).isEqualTo("\\value"); - verifyPlaceholderResolutions(resolver, "test"); + PlaceholderResolver resolver = mockPlaceholderResolver("p1", "v1", "p2", "v2", "p3", "v3", "p4", "v4"); + assertThat(this.parser.replacePlaceholders("\\${p1}", resolver)).isEqualTo("\\v1"); + assertThat(this.parser.replacePlaceholders("\\\\${p2}", resolver)).isEqualTo("\\\\v2"); + assertThat(this.parser.replacePlaceholders("\\${p3}\\", resolver)).isEqualTo("\\v3\\"); + assertThat(this.parser.replacePlaceholders("a\\${p4}\\z", resolver)).isEqualTo("a\\v4\\z"); + verifyPlaceholderResolutions(resolver, "p1", "p2", "p3", "p4"); } @Test - void textWithInvalidPlaceholderIsMerged() { + void textWithInvalidPlaceholderSyntaxIsMerged() { String text = "test${of${with${and${"; ParsedValue parsedValue = this.parser.parse(text); - assertThat(parsedValue.parts()).singleElement().isInstanceOfSatisfying( - TextPart.class, textPart -> assertThat(textPart.text()).isEqualTo(text)); + assertThat(parsedValue.parts()).singleElement().isInstanceOfSatisfying(TextPart.class, + textPart -> assertThat(textPart.text()).isEqualTo(text)); } } @@ -176,11 +169,11 @@ class DefaultValueTests { @ParameterizedTest(name = "{0} -> {1}") @MethodSource("placeholders") void placeholderIsReplaced(String text, String expected) { - Properties properties = new Properties(); - properties.setProperty("firstName", "John"); - properties.setProperty("nested0", "first"); - properties.setProperty("nested1", "Name"); - assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected); + Map properties = Map.of( + "firstName", "John", + "nested0", "first", + "nested1", "Name"); + assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected); } static Stream placeholders() { @@ -199,14 +192,14 @@ static Stream placeholders() { @ParameterizedTest(name = "{0} -> {1}") @MethodSource("nestedPlaceholders") void nestedPlaceholdersAreReplaced(String text, String expected) { - Properties properties = new Properties(); - properties.setProperty("p1", "v1"); - properties.setProperty("p2", "v2"); - properties.setProperty("p3", "${p1}:${p2}"); // nested placeholders - properties.setProperty("p4", "${p3}"); // deeply nested placeholders - properties.setProperty("p5", "${p1}:${p2}:${bogus}"); // unresolvable placeholder - properties.setProperty("p6", "${p1}:${p2}:${bogus:def}"); // unresolvable w/ default - assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected); + Map properties = Map.of( + "p1", "v1", + "p2", "v2", + "p3", "${p1}:${p2}", // nested placeholders + "p4", "${p3}", // deeply nested placeholders + "p5", "${p1}:${p2}:${bogus}", // unresolvable placeholder + "p6", "${p1}:${p2}:${bogus:def}"); // unresolvable w/ default + assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected); } static Stream nestedPlaceholders() { @@ -225,11 +218,11 @@ static Stream nestedPlaceholders() { @ParameterizedTest(name = "{0} -> {1}") @MethodSource("exactMatchPlaceholders") void placeholdersWithExactMatchAreConsidered(String text, String expected) { - Properties properties = new Properties(); - properties.setProperty("prefix://my-service", "example-service"); - properties.setProperty("px", "prefix"); - properties.setProperty("p1", "${prefix://my-service}"); - assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected); + Map properties = Map.of( + "prefix://my-service", "example-service", + "px", "prefix", + "p1", "${prefix://my-service}"); + assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected); } static Stream exactMatchPlaceholders() { @@ -242,74 +235,55 @@ static Stream exactMatchPlaceholders() { @Test void parseWithKeyEqualsToText() { PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "Steve"); - assertThat(this.parser.replacePlaceholders("${firstName}", resolver)) - .isEqualTo("Steve"); + assertThat(this.parser.replacePlaceholders("${firstName}", resolver)).isEqualTo("Steve"); verifyPlaceholderResolutions(resolver, "firstName"); } @Test void parseWithHardcodedFallback() { PlaceholderResolver resolver = mockPlaceholderResolver(); - assertThat(this.parser.replacePlaceholders("${firstName:Steve}", resolver)) - .isEqualTo("Steve"); + assertThat(this.parser.replacePlaceholders("${firstName:Steve}", resolver)).isEqualTo("Steve"); verifyPlaceholderResolutions(resolver, "firstName:Steve", "firstName"); } @Test void parseWithNestedPlaceholderInKeyUsingFallback() { PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John"); - assertThat(this.parser.replacePlaceholders("${first${invalid:Name}}", resolver)) - .isEqualTo("John"); + assertThat(this.parser.replacePlaceholders("${first${invalid:Name}}", resolver)).isEqualTo("John"); verifyPlaceholderResolutions(resolver, "invalid:Name", "invalid", "firstName"); } @Test void parseWithFallbackUsingPlaceholder() { PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John"); - assertThat(this.parser.replacePlaceholders("${invalid:${firstName}}", resolver)) - .isEqualTo("John"); + assertThat(this.parser.replacePlaceholders("${invalid:${firstName}}", resolver)).isEqualTo("John"); verifyPlaceholderResolutions(resolver, "invalid", "firstName"); } } - @Nested // Tests with the use of the escape character + /** + * Tests that use the escape character. + */ + @Nested class EscapedTests { private final PlaceholderParser parser = new PlaceholderParser("${", "}", ":", '\\', true); - @ParameterizedTest(name = "{0} -> {1}") - @MethodSource("escapedInNestedPlaceholders") - void escapedSeparatorInNestedPlaceholder(String text, String expected) { - Properties properties = new Properties(); - properties.setProperty("app.environment", "qa"); - properties.setProperty("app.service", "protocol"); - properties.setProperty("protocol://host/qa/name", "protocol://example.com/qa/name"); - properties.setProperty("service/host/qa/name", "https://example.com/qa/name"); - properties.setProperty("service/host/qa/name:value", "https://example.com/qa/name-value"); - assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected); - } - - static Stream escapedInNestedPlaceholders() { - return Stream.of( - Arguments.of("${protocol\\://host/${app.environment}/name}", "protocol://example.com/qa/name"), - Arguments.of("${${app.service}\\://host/${app.environment}/name}", "protocol://example.com/qa/name"), - Arguments.of("${service/host/${app.environment}/name:\\value}", "https://example.com/qa/name"), - Arguments.of("${service/host/${name\\:value}/}", "${service/host/${name:value}/}")); - } - @ParameterizedTest(name = "{0} -> {1}") @MethodSource("escapedPlaceholders") void escapedPlaceholderIsNotReplaced(String text, String expected) { - PlaceholderResolver resolver = mockPlaceholderResolver( - "firstName", "John", "nested0", "first", "nested1", "Name", + Map properties = Map.of( + "firstName", "John", "${test}", "John", - "p1", "v1", "p2", "\\${p1:default}", "p3", "${p2}", + "p1", "v1", + "p2", "\\${p1:default}", + "p3", "${p2}", "p4", "adc${p0:\\${p1}}", "p5", "adc${\\${p0}:${p1}}", "p6", "adc${p0:def\\${p1}}", "p7", "adc\\${"); - assertThat(this.parser.replacePlaceholders(text, resolver)).isEqualTo(expected); + assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected); } static Stream escapedPlaceholders() { @@ -324,18 +298,21 @@ static Stream escapedPlaceholders() { Arguments.of("${p4}", "adc${p1}"), Arguments.of("${p5}", "adcv1"), Arguments.of("${p6}", "adcdef${p1}"), - Arguments.of("${p7}", "adc\\${")); - + Arguments.of("${p7}", "adc\\${"), + // Double backslash + Arguments.of("DOMAIN\\\\${user.name}", "DOMAIN\\${user.name}"), + // Triple backslash + Arguments.of("triple\\\\\\${backslash}", "triple\\\\${backslash}"), + // Multiple escaped placeholders + Arguments.of("start\\${prop1}middle\\${prop2}end", "start${prop1}middle${prop2}end") + ); } @ParameterizedTest(name = "{0} -> {1}") @MethodSource("escapedSeparators") void escapedSeparatorIsNotReplaced(String text, String expected) { - Properties properties = new Properties(); - properties.setProperty("first:Name", "John"); - properties.setProperty("nested0", "first"); - properties.setProperty("nested1", "Name"); - assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected); + Map properties = Map.of("first:Name", "John"); + assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected); } static Stream escapedSeparators() { @@ -345,6 +322,26 @@ static Stream escapedSeparators() { ); } + @ParameterizedTest(name = "{0} -> {1}") + @MethodSource("escapedSeparatorsInNestedPlaceholders") + void escapedSeparatorInNestedPlaceholderIsNotReplaced(String text, String expected) { + Map properties = Map.of( + "app.environment", "qa", + "app.service", "protocol", + "protocol://host/qa/name", "protocol://example.com/qa/name", + "service/host/qa/name", "https://example.com/qa/name", + "service/host/qa/name:value", "https://example.com/qa/name-value"); + assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected); + } + + static Stream escapedSeparatorsInNestedPlaceholders() { + return Stream.of( + Arguments.of("${protocol\\://host/${app.environment}/name}", "protocol://example.com/qa/name"), + Arguments.of("${${app.service}\\://host/${app.environment}/name}", "protocol://example.com/qa/name"), + Arguments.of("${service/host/${app.environment}/name:\\value}", "https://example.com/qa/name"), + Arguments.of("${service/host/${name\\:value}/}", "${service/host/${name:value}/}")); + } + } @Nested @@ -354,34 +351,38 @@ class ExceptionTests { @Test void textWithCircularReference() { - PlaceholderResolver resolver = mockPlaceholderResolver("pL", "${pR}", "pR", "${pL}"); - assertThatThrownBy(() -> this.parser.replacePlaceholders("${pL}", resolver)) + Map properties = Map.of( + "pL", "${pR}", + "pR", "${pL}"); + assertThatThrownBy(() -> this.parser.replacePlaceholders("${pL}", properties::get)) .isInstanceOf(PlaceholderResolutionException.class) .hasMessage("Circular placeholder reference 'pL' in value \"${pL}\" <-- \"${pR}\" <-- \"${pL}\""); } @Test void unresolvablePlaceholderIsReported() { - PlaceholderResolver resolver = mockPlaceholderResolver(); assertThatExceptionOfType(PlaceholderResolutionException.class) - .isThrownBy(() -> this.parser.replacePlaceholders("${bogus}", resolver)) - .withMessage("Could not resolve placeholder 'bogus' in value \"${bogus}\"") + .isThrownBy(() -> this.parser.replacePlaceholders("X${bogus}Z", placeholderName -> null)) + .withMessage("Could not resolve placeholder 'bogus' in value \"X${bogus}Z\"") .withNoCause(); } @Test void unresolvablePlaceholderInNestedPlaceholderIsReportedWithChain() { - PlaceholderResolver resolver = mockPlaceholderResolver("p1", "v1", "p2", "v2", + Map properties = Map.of( + "p1", "v1", + "p2", "v2", "p3", "${p1}:${p2}:${bogus}"); assertThatExceptionOfType(PlaceholderResolutionException.class) - .isThrownBy(() -> this.parser.replacePlaceholders("${p3}", resolver)) + .isThrownBy(() -> this.parser.replacePlaceholders("${p3}", properties::get)) .withMessage("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\" <-- \"${p3}\"") .withNoCause(); } } - PlaceholderResolver mockPlaceholderResolver(String... pairs) { + + private static PlaceholderResolver mockPlaceholderResolver(String... pairs) { if (pairs.length % 2 == 1) { throw new IllegalArgumentException("size must be even, it is a set of key=value pairs"); } @@ -394,7 +395,7 @@ PlaceholderResolver mockPlaceholderResolver(String... pairs) { return resolver; } - void verifyPlaceholderResolutions(PlaceholderResolver mock, String... placeholders) { + private static void verifyPlaceholderResolutions(PlaceholderResolver mock, String... placeholders) { InOrder ordered = inOrder(mock); for (String placeholder : placeholders) { ordered.verify(mock).resolvePlaceholder(placeholder); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java index 99daa91494a2..84c2f7981f98 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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,8 +16,11 @@ package org.springframework.jdbc.core.simple; +import java.util.List; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassRelativeResourceLoader; @@ -144,6 +147,86 @@ void updateWithGeneratedKeysAndKeyColumnNamesUsingNamedParameters() { } + @Nested // gh-34768 + class ReusedNamedParameterTests { + + private static final String QUERY1 = """ + select * from users + where + first_name in ('Bogus', :name) or + last_name in (:name, 'Bogus') + order by last_name + """; + + private static final String QUERY2 = """ + select * from users + where + first_name in (:names) or + last_name in (:names) + order by last_name + """; + + + @BeforeEach + void insertTestUsers() { + jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).params("John", "John").update(); + jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).params("John", "Smith").update(); + jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).params("Smith", "Smith").update(); + assertNumUsers(4); + } + + @Test + void selectWithReusedNamedParameter() { + List users = jdbcClient.sql(QUERY1) + .param("name", "John") + .query(User.class) + .list(); + + assertResults(users); + } + + @Test + void selectWithReusedNamedParameterFromBeanProperties() { + List users = jdbcClient.sql(QUERY1) + .paramSource(new Name("John")) + .query(User.class) + .list(); + + assertResults(users); + } + + @Test + void selectWithReusedNamedParameterList() { + List users = jdbcClient.sql(QUERY2) + .param("names", List.of("John", "Bogus")) + .query(User.class) + .list(); + + assertResults(users); + } + + @Test + void selectWithReusedNamedParameterListFromBeanProperties() { + List users = jdbcClient.sql(QUERY2) + .paramSource(new Names(List.of("John", "Bogus"))) + .query(User.class) + .list(); + + assertResults(users); + } + + + private static void assertResults(List users) { + assertThat(users).containsExactly(new User(2, "John", "John"), new User(3, "John", "Smith")); + } + + record Name(String name) {} + + record Names(List names) {} + + } + + private void assertNumUsers(long count) { long numUsers = this.jdbcClient.sql("select count(id) from users").query(Long.class).single(); assertThat(numUsers).isEqualTo(count); diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java index f6696e8d9cd9..76b7adfcb879 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -49,6 +49,7 @@ * @author Juergen Hoeller * @author Mark Paluch * @author Anton Naydenov + * @author Sam Brannen * @since 5.3 */ abstract class NamedParameterUtils { @@ -513,37 +514,75 @@ private static class ExpandedQuery implements PreparedOperation { private final BindParameterSource parameterSource; + ExpandedQuery(String expandedSql, NamedParameters parameters, BindParameterSource parameterSource) { this.expandedSql = expandedSql; this.parameters = parameters; this.parameterSource = parameterSource; } - @SuppressWarnings({"rawtypes", "unchecked"}) - public void bind(BindTarget target, String identifier, Parameter parameter) { - List bindMarkers = getBindMarkers(identifier); + + @Override + public String toQuery() { + return this.expandedSql; + } + + @Override + public String getSource() { + return this.expandedSql; + } + + @Override + public void bindTo(BindTarget target) { + for (String namedParameter : this.parameterSource.getParameterNames()) { + Parameter parameter = this.parameterSource.getValue(namedParameter); + if (parameter.getValue() == null) { + bindNull(target, namedParameter, parameter); + } + else { + bind(target, namedParameter, parameter); + } + } + } + + private void bindNull(BindTarget target, String identifier, Parameter parameter) { + List> bindMarkers = getBindMarkers(identifier); + if (bindMarkers == null) { + target.bind(identifier, parameter); + return; + } + for (List outer : bindMarkers) { + for (BindMarker bindMarker : outer) { + bindMarker.bind(target, parameter); + } + } + } + + private void bind(BindTarget target, String identifier, Parameter parameter) { + List> bindMarkers = getBindMarkers(identifier); if (bindMarkers == null) { target.bind(identifier, parameter); return; } - if (parameter.getValue() instanceof Collection collection) { - Iterator iterator = collection.iterator(); - Iterator markers = bindMarkers.iterator(); - while (iterator.hasNext()) { - Object valueToBind = iterator.next(); - if (valueToBind instanceof Object[] objects) { - for (Object object : objects) { - bind(target, markers, object); + + for (List outer : bindMarkers) { + if (parameter.getValue() instanceof Collection collection) { + Iterator markers = outer.iterator(); + for (Object valueToBind : collection) { + if (valueToBind instanceof Object[] objects) { + for (Object object : objects) { + bind(target, markers, object); + } + } + else { + bind(target, markers, valueToBind); } - } - else { - bind(target, markers, valueToBind); } } - } - else { - for (BindMarker bindMarker : bindMarkers) { - bindMarker.bind(target, parameter); + else { + for (BindMarker bindMarker : outer) { + bindMarker.bind(target, parameter); + } } } } @@ -555,52 +594,19 @@ private void bind(BindTarget target, Iterator markers, Object valueT markers.next().bind(target, valueToBind); } - public void bindNull(BindTarget target, String identifier, Parameter parameter) { - List bindMarkers = getBindMarkers(identifier); - if (bindMarkers == null) { - target.bind(identifier, parameter); - return; - } - for (BindMarker bindMarker : bindMarkers) { - bindMarker.bind(target, parameter); - } - } - @Nullable - List getBindMarkers(String identifier) { + private List> getBindMarkers(String identifier) { List parameters = this.parameters.getMarker(identifier); if (parameters == null) { return null; } - List markers = new ArrayList<>(); + List> markers = new ArrayList<>(); for (NamedParameters.NamedParameter parameter : parameters) { - markers.addAll(parameter.placeholders); + markers.add(new ArrayList<>(parameter.placeholders)); } return markers; } - @Override - public String getSource() { - return this.expandedSql; - } - - @Override - public void bindTo(BindTarget target) { - for (String namedParameter : this.parameterSource.getParameterNames()) { - Parameter parameter = this.parameterSource.getValue(namedParameter); - if (parameter.getValue() == null) { - bindNull(target, namedParameter, parameter); - } - else { - bind(target, namedParameter, parameter); - } - } - } - - @Override - public String toQuery() { - return this.expandedSql; - } } } diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/AnonymousBindMarkers.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/AnonymousBindMarkers.java index 71569702ccc7..c66ada2ca172 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/AnonymousBindMarkers.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/AnonymousBindMarkers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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. @@ -19,8 +19,9 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /** - * Anonymous, index-based bind marker using a static placeholder. - * Instances are bound by the ordinal position ordered by the appearance of + * Anonymous, index-based bind markers that use a static placeholder. + * + *

Instances are bound by the ordinal position ordered by the appearance of * the placeholder. This implementation creates indexed bind markers using * an anonymous placeholder that correlates with an index. * @@ -46,7 +47,7 @@ class AnonymousBindMarkers implements BindMarkers { /** - * Create a new {@link AnonymousBindMarkers} instance given {@code placeholder}. + * Create a new {@link AnonymousBindMarkers} instance for the given {@code placeholder}. * @param placeholder parameter bind marker */ AnonymousBindMarkers(String placeholder) { diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarker.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarker.java index 5fb53c47c190..40f8a71313e6 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarker.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarker.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 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. @@ -20,7 +20,8 @@ /** * A bind marker represents a single bindable parameter within a query. - * Bind markers are dialect-specific and provide a + * + *

Bind markers are dialect-specific and provide a * {@link #getPlaceholder() placeholder} that is used in the actual query. * * @author Mark Paluch @@ -37,7 +38,8 @@ public interface BindMarker { String getPlaceholder(); /** - * Bind the given {@code value} to the {@link Statement} using the underlying binding strategy. + * Bind the given {@code value} to the {@link Statement} using the underlying + * binding strategy. * @param bindTarget the target to bind the value to * @param value the actual value (must not be {@code null}; * use {@link #bindNull(BindTarget, Class)} for {@code null} values) @@ -46,7 +48,8 @@ public interface BindMarker { void bind(BindTarget bindTarget, Object value); /** - * Bind a {@code null} value to the {@link Statement} using the underlying binding strategy. + * Bind a {@code null} value to the {@link Statement} using the underlying + * binding strategy. * @param bindTarget the target to bind the value to * @param valueType the value type (must not be {@code null}) * @see Statement#bindNull diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkers.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkers.java index af5a9fd48ca2..5504cbc01e1d 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkers.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 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. @@ -20,10 +20,10 @@ * Bind markers represent placeholders in SQL queries for substitution * for an actual parameter. Using bind markers allows creating safe queries * so query strings are not required to contain escaped values but rather - * the driver encodes parameter in the appropriate representation. + * the driver encodes the parameter in the appropriate representation. * *

{@link BindMarkers} is stateful and can be only used for a single binding - * pass of one or more parameters. It maintains bind indexes/bind parameter names. + * pass of one or more parameters. It maintains bind indexes or bind parameter names. * * @author Mark Paluch * @since 5.3 @@ -41,7 +41,7 @@ public interface BindMarkers { /** * Create a new {@link BindMarker} that accepts a {@code hint}. - * Implementations are allowed to consider/ignore/filter + *

Implementations are allowed to consider/ignore/filter * the name hint to create more expressive bind markers. * @param hint an optional name hint that can be used as part of the bind marker * @return a new {@link BindMarker} diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkersFactoryResolver.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkersFactoryResolver.java index c658352846d7..d83c70abdd57 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkersFactoryResolver.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkersFactoryResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -30,8 +30,8 @@ /** * Resolves a {@link BindMarkersFactory} from a {@link ConnectionFactory} using - * {@link BindMarkerFactoryProvider}. Dialect resolution uses Spring's - * {@link SpringFactoriesLoader spring.factories} to determine available extensions. + * a {@link BindMarkerFactoryProvider}. Dialect resolution uses Spring's + * {@link SpringFactoriesLoader spring.factories} file to determine available extensions. * * @author Mark Paluch * @since 5.3 @@ -45,8 +45,8 @@ public final class BindMarkersFactoryResolver { /** - * Retrieve a {@link BindMarkersFactory} by inspecting {@link ConnectionFactory} - * and its metadata. + * Retrieve a {@link BindMarkersFactory} by inspecting the supplied + * {@link ConnectionFactory} and its metadata. * @param connectionFactory the connection factory to inspect * @return the resolved {@link BindMarkersFactory} * @throws NoBindMarkersFactoryException if no {@link BindMarkersFactory} can be resolved @@ -69,18 +69,21 @@ private BindMarkersFactoryResolver() { /** - * SPI to extend Spring's default R2DBC BindMarkersFactory discovery mechanism. - * Implementations of this interface are discovered through Spring's + * SPI to extend Spring's default R2DBC {@link BindMarkersFactory} discovery + * mechanism. + * + *

Implementations of this interface are discovered through Spring's * {@link SpringFactoriesLoader} mechanism. + * * @see SpringFactoriesLoader */ @FunctionalInterface public interface BindMarkerFactoryProvider { /** - * Return a {@link BindMarkersFactory} for a {@link ConnectionFactory}. - * @param connectionFactory the connection factory to be used with the {@link BindMarkersFactory} - * @return the {@link BindMarkersFactory} if the {@link BindMarkerFactoryProvider} + * Return a {@link BindMarkersFactory} for the given {@link ConnectionFactory}. + * @param connectionFactory the connection factory to be used with the {@code BindMarkersFactory} + * @return the {@code BindMarkersFactory} if this {@code BindMarkerFactoryProvider} * can provide a bind marker factory object, otherwise {@code null} */ @Nullable @@ -89,7 +92,7 @@ public interface BindMarkerFactoryProvider { /** - * Exception thrown when {@link BindMarkersFactoryResolver} cannot resolve a + * Exception thrown when a {@link BindMarkersFactoryResolver} cannot resolve a * {@link BindMarkersFactory}. */ @SuppressWarnings("serial") @@ -106,8 +109,11 @@ public NoBindMarkersFactoryException(String msg) { /** - * Built-in bind maker factories. Used typically as last {@link BindMarkerFactoryProvider} - * when other providers register with a higher precedence. + * Built-in bind marker factories. + * + *

Typically used as the last {@link BindMarkerFactoryProvider} when other + * providers are registered with a higher precedence. + * * @see org.springframework.core.Ordered * @see org.springframework.core.annotation.AnnotationAwareOrderComparator */ diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/IndexedBindMarkers.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/IndexedBindMarkers.java index 5f14d937b4f1..23081c9778cb 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/IndexedBindMarkers.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/IndexedBindMarkers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 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. @@ -19,7 +19,7 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /** - * Index-based bind marker. This implementation creates indexed bind + * Index-based bind markers. This implementation creates indexed bind * markers using a numeric index and an optional prefix for bind markers * to be represented within the query string. * @@ -43,14 +43,15 @@ class IndexedBindMarkers implements BindMarkers { /** - * Create a new {@link IndexedBindMarker} instance given {@code prefix} and {@code beginWith}. - * @param prefix bind parameter prefix - * @param beginWith the first index to use + * Create a new {@link IndexedBindMarker} instance for the given {@code prefix} + * and {@code beginWith} value. + * @param prefix the bind parameter prefix + * @param beginIndex the first index to use */ - IndexedBindMarkers(String prefix, int beginWith) { + IndexedBindMarkers(String prefix, int beginIndex) { this.counter = 0; this.prefix = prefix; - this.offset = beginWith; + this.offset = beginIndex; } diff --git a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/AbstractDatabaseClientIntegrationTests.java b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/AbstractDatabaseClientIntegrationTests.java index 19332ecd4033..c992aa242f9b 100644 --- a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/AbstractDatabaseClientIntegrationTests.java +++ b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/AbstractDatabaseClientIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -23,6 +23,7 @@ import io.r2dbc.spi.Parameters; import io.r2dbc.spi.Result; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -38,6 +39,7 @@ * @author Mark Paluch * @author Mingyuan Wu * @author Juergen Hoeller + * @author Sam Brannen */ abstract class AbstractDatabaseClientIntegrationTests { @@ -121,7 +123,8 @@ void executeInsertWithMap() { DatabaseClient databaseClient = DatabaseClient.create(connectionFactory); databaseClient.sql("INSERT INTO legoset (id, name, manual) VALUES(:id, :name, :manual)") - .bindValues(Map.of("id", 42055, + .bindValues(Map.of( + "id", 42055, "name", Parameters.in("SCHAUFELRADBAGGER"), "manual", Parameters.in(Integer.class))) .fetch().rowsUpdated() @@ -199,8 +202,7 @@ void executeDeferred() { void shouldEmitGeneratedKey() { DatabaseClient databaseClient = DatabaseClient.create(connectionFactory); - databaseClient.sql( - "INSERT INTO legoset ( name, manual) VALUES(:name, :manual)") + databaseClient.sql("INSERT INTO legoset ( name, manual) VALUES(:name, :manual)") .bind("name","SCHAUFELRADBAGGER") .bindNull("manual", Integer.class) .filter(statement -> statement.returnGeneratedValues("id")) @@ -212,6 +214,129 @@ void shouldEmitGeneratedKey() { } + @Nested + class ReusedNamedParameterTests { + + @Test // gh-34768 + void executeInsertWithReusedNamedParameter() { + DatabaseClient databaseClient = DatabaseClient.create(connectionFactory); + + Lego lego = new Lego(1, 42, "Star Wars", 42); + + // ":number" is reused. + databaseClient.sql("INSERT INTO legoset (id, version, name, manual) VALUES(:id, :number, :name, :number)") + .bind("id", lego.id) + .bind("name", lego.name) + .bind("number", lego.version) + .fetch().rowsUpdated() + .as(StepVerifier::create) + .expectNext(1L) + .verifyComplete(); + + databaseClient.sql("SELECT * FROM legoset") + .mapProperties(Lego.class) + .first() + .as(StepVerifier::create) + .assertNext(actual -> assertThat(actual).isEqualTo(lego)) + .verifyComplete(); + } + + @Test // gh-34768 + void executeSelectWithReusedNamedParameterList() { + DatabaseClient databaseClient = DatabaseClient.create(connectionFactory); + + String insertSql = "INSERT INTO legoset (id, version, name, manual) VALUES(:id, :version, :name, :manual)"; + // ":numbers" is reused. + String selectSql = "SELECT * FROM legoset WHERE version IN (:numbers) OR manual IN (:numbers)"; + Lego lego = new Lego(1, 42, "Star Wars", 99); + + databaseClient.sql(insertSql) + .bind("id", lego.id) + .bind("version", lego.version) + .bind("name", lego.name) + .bind("manual", lego.manual) + .fetch().rowsUpdated() + .as(StepVerifier::create) + .expectNext(1L) + .verifyComplete(); + + databaseClient.sql(selectSql) + // match version + .bind("numbers", List.of(2, 3, lego.version, 4)) + .mapProperties(Lego.class) + .first() + .as(StepVerifier::create) + .assertNext(actual -> assertThat(actual).isEqualTo(lego)) + .verifyComplete(); + + databaseClient.sql(selectSql) + // match manual + .bind("numbers", List.of(2, 3, lego.manual, 4)) + .mapProperties(Lego.class) + .first() + .as(StepVerifier::create) + .assertNext(actual -> assertThat(actual).isEqualTo(lego)) + .verifyComplete(); + } + + @Test // gh-34768 + void executeSelectWithReusedNamedParameterListFromBeanProperties() { + DatabaseClient databaseClient = DatabaseClient.create(connectionFactory); + + String insertSql = "INSERT INTO legoset (id, version, name, manual) VALUES(:id, :version, :name, :manual)"; + // ":numbers" is reused. + String selectSql = "SELECT * FROM legoset WHERE version IN (:numbers) OR manual IN (:numbers)"; + Lego lego = new Lego(1, 42, "Star Wars", 99); + + databaseClient.sql(insertSql) + .bind("id", lego.id) + .bind("version", lego.version) + .bind("name", lego.name) + .bind("manual", lego.manual) + .fetch().rowsUpdated() + .as(StepVerifier::create) + .expectNext(1L) + .verifyComplete(); + + databaseClient.sql(selectSql) + // match version + .bindProperties(new LegoRequest(List.of(lego.version))) + .mapProperties(Lego.class) + .first() + .as(StepVerifier::create) + .assertNext(actual -> assertThat(actual).isEqualTo(lego)) + .verifyComplete(); + + databaseClient.sql(selectSql) + // match manual + .bindProperties(new LegoRequest(List.of(lego.manual))) + .mapProperties(Lego.class) + .first() + .as(StepVerifier::create) + .assertNext(actual -> assertThat(actual).isEqualTo(lego)) + .verifyComplete(); + } + + + record Lego(int id, Integer version, String name, Integer manual) { + } + + static class LegoRequest { + + private final List numbers; + + LegoRequest(List numbers) { + this.numbers = numbers; + } + + public List getNumbers() { + return numbers; + } + } + + } + + record ParameterRecord(int id, String name, Integer manual) { } diff --git a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsTests.java b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsTests.java index 6d23d4810371..725bcf72a924 100644 --- a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsTests.java +++ b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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,10 +16,8 @@ package org.springframework.r2dbc.core; -import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import io.r2dbc.spi.Parameters; @@ -29,8 +27,6 @@ import org.springframework.r2dbc.core.binding.BindMarkersFactory; import org.springframework.r2dbc.core.binding.BindTarget; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -42,10 +38,13 @@ * @author Mark Paluch * @author Jens Schauder * @author Anton Naydenov + * @author Sam Brannen */ class NamedParameterUtilsTests { - private final BindMarkersFactory BIND_MARKERS = BindMarkersFactory.indexed("$", 1); + private static final BindMarkersFactory INDEXED_MARKERS = BindMarkersFactory.indexed("$", 1); + + private static final BindMarkersFactory ANONYMOUS_MARKERS = BindMarkersFactory.anonymous("?"); @Test @@ -73,7 +72,7 @@ void substituteNamedParameters() { namedParams.addValue("a", "a").addValue("b", "b").addValue("c", "c"); PreparedOperation operation = NamedParameterUtils.substituteNamedParameters( - "xxx :a :b :c", BIND_MARKERS, namedParams); + "xxx :a :b :c", INDEXED_MARKERS, namedParams); assertThat(operation.toQuery()).isEqualTo("xxx $1 $2 $3"); @@ -87,11 +86,11 @@ void substituteNamedParameters() { void substituteObjectArray() { MapBindParameterSource namedParams = new MapBindParameterSource(new HashMap<>()); namedParams.addValue("a", - Arrays.asList(new Object[] {"Walter", "Heisenberg"}, - new Object[] {"Walt Jr.", "Flynn"})); + List.of(new Object[] {"Walter", "Heisenberg"}, + new Object[] {"Walt Jr.", "Flynn"})); PreparedOperation operation = NamedParameterUtils.substituteNamedParameters( - "xxx :a", BIND_MARKERS, namedParams); + "xxx :a", INDEXED_MARKERS, namedParams); assertThat(operation.toQuery()).isEqualTo("xxx ($1, $2), ($3, $4)"); } @@ -100,13 +99,13 @@ void substituteObjectArray() { void shouldBindObjectArray() { MapBindParameterSource namedParams = new MapBindParameterSource(new HashMap<>()); namedParams.addValue("a", - Arrays.asList(new Object[] {"Walter", "Heisenberg"}, - new Object[] {"Walt Jr.", "Flynn"})); + List.of(new Object[] {"Walter", "Heisenberg"}, + new Object[] {"Walt Jr.", "Flynn"})); BindTarget bindTarget = mock(); PreparedOperation operation = NamedParameterUtils.substituteNamedParameters( - "xxx :a", BIND_MARKERS, namedParams); + "xxx :a", INDEXED_MARKERS, namedParams); operation.bindTo(bindTarget); verify(bindTarget).bind(0, "Walter"); @@ -141,7 +140,7 @@ void parseSqlStatementWithPostgresCasting() { ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql); PreparedOperation operation = NamedParameterUtils.substituteNamedParameters( - parsedSql, BIND_MARKERS, new MapBindParameterSource()); + parsedSql, INDEXED_MARKERS, new MapBindParameterSource()); assertThat(operation.toQuery()).isEqualTo(expectedSql); } @@ -312,157 +311,139 @@ void shouldAllowParsingMultipleUseOfParameter() { void multipleEqualParameterReferencesBindsValueOnce() { String sql = "SELECT * FROM person where name = :id or lastname = :id"; - BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0); - - PreparedOperation operation = NamedParameterUtils.substituteNamedParameters( - sql, factory, new MapBindParameterSource( - Collections.singletonMap("id", Parameters.in("foo")))); - - assertThat(operation.toQuery()).isEqualTo( - "SELECT * FROM person where name = $0 or lastname = $0"); - - operation.bindTo(new BindTarget() { - @Override - public void bind(String identifier, Object value) { - throw new UnsupportedOperationException(); - } - @Override - public void bind(int index, Object value) { - assertThat(index).isEqualTo(0); - assertThat(value).isEqualTo(Parameters.in("foo")); - } - @Override - public void bindNull(String identifier, Class type) { - throw new UnsupportedOperationException(); - } - @Override - public void bindNull(int index, Class type) { - throw new UnsupportedOperationException(); - } - }); + MapBindParameterSource source = new MapBindParameterSource(Map.of("id", Parameters.in("foo"))); + PreparedOperation operation = NamedParameterUtils.substituteNamedParameters(sql, INDEXED_MARKERS, source); + + assertThat(operation.toQuery()) + .isEqualTo("SELECT * FROM person where name = $1 or lastname = $1"); + + TrackingBindTarget trackingBindTarget = new TrackingBindTarget(); + + operation.bindTo(trackingBindTarget); + + assertThat(trackingBindTarget.bindings) + .hasSize(1) + .containsEntry(0, Parameters.in("foo")); } @Test - void multipleEqualCollectionParameterReferencesBindsValueOnce() { + void multipleEqualCollectionParameterReferencesForIndexedMarkersBindsValuesOnce() { String sql = "SELECT * FROM person where name IN (:ids) or lastname IN (:ids)"; - BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0); - - MultiValueMap bindings = new LinkedMultiValueMap<>(); - - PreparedOperation operation = NamedParameterUtils.substituteNamedParameters( - sql, factory, new MapBindParameterSource(Collections.singletonMap("ids", - Parameters.in(Arrays.asList("foo", "bar", "baz"))))); - - assertThat(operation.toQuery()).isEqualTo( - "SELECT * FROM person where name IN ($0, $1, $2) or lastname IN ($0, $1, $2)"); - - operation.bindTo(new BindTarget() { - @Override - public void bind(String identifier, Object value) { - throw new UnsupportedOperationException(); - } - @Override - public void bind(int index, Object value) { - assertThat(index).isIn(0, 1, 2); - assertThat(value).isIn("foo", "bar", "baz"); - bindings.add(index, value); - } - @Override - public void bindNull(String identifier, Class type) { - throw new UnsupportedOperationException(); - } - @Override - public void bindNull(int index, Class type) { - throw new UnsupportedOperationException(); - } - }); - - assertThat(bindings).containsEntry(0, Collections.singletonList("foo")) // - .containsEntry(1, Collections.singletonList("bar")) // - .containsEntry(2, Collections.singletonList("baz")); + MapBindParameterSource source = new MapBindParameterSource(Map.of("ids", + Parameters.in(List.of("foo", "bar", "baz")))); + PreparedOperation operation = NamedParameterUtils.substituteNamedParameters(sql, INDEXED_MARKERS, source); + + assertThat(operation.toQuery()) + .isEqualTo("SELECT * FROM person where name IN ($1, $2, $3) or lastname IN ($1, $2, $3)"); + + TrackingBindTarget trackingBindTarget = new TrackingBindTarget(); + + operation.bindTo(trackingBindTarget); + + assertThat(trackingBindTarget.bindings) + .hasSize(3) + .containsEntry(0, "foo") + .containsEntry(1, "bar") + .containsEntry(2, "baz"); + } + + @Test // gh-34768 + void multipleEqualCollectionParameterReferencesForAnonymousMarkersBindsValuesTwice() { + String sql = "SELECT * FROM fund_info WHERE fund_code IN (:fundCodes) OR fund_code IN (:fundCodes)"; + + MapBindParameterSource source = new MapBindParameterSource(Map.of("fundCodes", Parameters.in(List.of("foo", "bar", "baz")))); + PreparedOperation operation = NamedParameterUtils.substituteNamedParameters(sql, ANONYMOUS_MARKERS, source); + + assertThat(operation.toQuery()) + .isEqualTo("SELECT * FROM fund_info WHERE fund_code IN (?, ?, ?) OR fund_code IN (?, ?, ?)"); + + TrackingBindTarget trackingBindTarget = new TrackingBindTarget(); + + operation.bindTo(trackingBindTarget); + + assertThat(trackingBindTarget.bindings) + .hasSize(6) + .containsEntry(0, "foo") + .containsEntry(1, "bar") + .containsEntry(2, "baz") + .containsEntry(3, "foo") + .containsEntry(4, "bar") + .containsEntry(5, "baz"); } @Test - void multipleEqualParameterReferencesForAnonymousMarkersBindsValueMultipleTimes() { + void multipleEqualParameterReferencesForAnonymousMarkersBindsValueTwice() { String sql = "SELECT * FROM person where name = :id or lastname = :id"; - BindMarkersFactory factory = BindMarkersFactory.anonymous("?"); - - PreparedOperation operation = NamedParameterUtils.substituteNamedParameters( - sql, factory, new MapBindParameterSource( - Collections.singletonMap("id", Parameters.in("foo")))); - - assertThat(operation.toQuery()).isEqualTo( - "SELECT * FROM person where name = ? or lastname = ?"); - - Map bindValues = new LinkedHashMap<>(); - - operation.bindTo(new BindTarget() { - @Override - public void bind(String identifier, Object value) { - throw new UnsupportedOperationException(); - } - @Override - public void bind(int index, Object value) { - bindValues.put(index, value); - } - @Override - public void bindNull(String identifier, Class type) { - throw new UnsupportedOperationException(); - } - @Override - public void bindNull(int index, Class type) { - throw new UnsupportedOperationException(); - } - }); - - assertThat(bindValues).hasSize(2).containsEntry(0, Parameters.in("foo")).containsEntry(1, Parameters.in("foo")); + MapBindParameterSource source = new MapBindParameterSource(Map.of("id", Parameters.in("foo"))); + PreparedOperation operation = NamedParameterUtils.substituteNamedParameters(sql, ANONYMOUS_MARKERS, source); + + assertThat(operation.toQuery()) + .isEqualTo("SELECT * FROM person where name = ? or lastname = ?"); + + TrackingBindTarget trackingBindTarget = new TrackingBindTarget(); + + operation.bindTo(trackingBindTarget); + + assertThat(trackingBindTarget.bindings) + .hasSize(2) + .containsEntry(0, Parameters.in("foo")) + .containsEntry(1, Parameters.in("foo")); } @Test void multipleEqualParameterReferencesBindsNullOnce() { String sql = "SELECT * FROM person where name = :id or lastname = :id"; - BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0); - - PreparedOperation operation = NamedParameterUtils.substituteNamedParameters( - sql, factory, new MapBindParameterSource( - Collections.singletonMap("id", Parameters.in(String.class)))); - - assertThat(operation.toQuery()).isEqualTo( - "SELECT * FROM person where name = $0 or lastname = $0"); - - operation.bindTo(new BindTarget() { - @Override - public void bind(String identifier, Object value) { - throw new UnsupportedOperationException(); - } - @Override - public void bind(int index, Object value) { - assertThat(index).isEqualTo(0); - assertThat(value).isEqualTo(Parameters.in(String.class)); - } - @Override - public void bindNull(String identifier, Class type) { - throw new UnsupportedOperationException(); - } - @Override - public void bindNull(int index, Class type) { - throw new UnsupportedOperationException(); - } - }); + MapBindParameterSource source = new MapBindParameterSource(Map.of("id", Parameters.in(String.class))); + PreparedOperation operation = NamedParameterUtils.substituteNamedParameters(sql, INDEXED_MARKERS, source); + + assertThat(operation.toQuery()) + .isEqualTo("SELECT * FROM person where name = $1 or lastname = $1"); + + TrackingBindTarget trackingBindTarget = new TrackingBindTarget(); + + operation.bindTo(trackingBindTarget); + + assertThat(trackingBindTarget.bindings) + .hasSize(1) + .containsEntry(0, Parameters.in(String.class)); } - private String expand(ParsedSql sql) { - return NamedParameterUtils.substituteNamedParameters(sql, BIND_MARKERS, + private static String expand(ParsedSql sql) { + return NamedParameterUtils.substituteNamedParameters(sql, INDEXED_MARKERS, new MapBindParameterSource()).toQuery(); } - private String expand(String sql) { - return NamedParameterUtils.substituteNamedParameters(sql, BIND_MARKERS, + private static String expand(String sql) { + return NamedParameterUtils.substituteNamedParameters(sql, INDEXED_MARKERS, new MapBindParameterSource()).toQuery(); } + + private static class TrackingBindTarget implements BindTarget { + + final Map bindings = new HashMap<>(); + + @Override + public void bind(String identifier, Object value) {} + + @Override + public void bind(int index, Object value) { + this.bindings.put(index, value); + } + + @Override + public void bindNull(String identifier, Class type) { + throw new UnsupportedOperationException(); + } + + @Override + public void bindNull(int index, Class type) { + throw new UnsupportedOperationException(); + } + } + } diff --git a/spring-test/spring-test.gradle b/spring-test/spring-test.gradle index f0bc5a7f635f..b8abb591b2c8 100644 --- a/spring-test/spring-test.gradle +++ b/spring-test/spring-test.gradle @@ -105,14 +105,10 @@ test { description = "Runs JUnit 4, JUnit Jupiter, and TestNG tests." useJUnitPlatform { includeEngines "junit-vintage", "junit-jupiter", "testng" - excludeTags "failing-test-case" } - // We use `include` instead of `filter.includeTestsMatching`, since - // the latter results in some tests being executed/reported - // multiple times. - include(["**/*Tests.class", "**/*Test.class"]) + // `include` test filters and system properties are configured in + // org.springframework.build.TestConventions in buildSrc. filter.excludeTestsMatching("*TestCase") - systemProperty("testGroups", project.properties.get("testGroups")) - // Java Util Logging for the JUnit Platform. + // Optionally configure Java Util Logging for the JUnit Platform. // systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") } diff --git a/spring-test/src/main/java/org/springframework/mock/web/server/MockServerWebExchange.java b/spring-test/src/main/java/org/springframework/mock/web/server/MockServerWebExchange.java index bf5f0b37ba80..bd7dbb03c687 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/server/MockServerWebExchange.java +++ b/spring-test/src/main/java/org/springframework/mock/web/server/MockServerWebExchange.java @@ -16,6 +16,8 @@ package org.springframework.mock.web.server; +import java.security.Principal; + import reactor.core.publisher.Mono; import org.springframework.context.ApplicationContext; @@ -40,15 +42,19 @@ */ public final class MockServerWebExchange extends DefaultServerWebExchange { + private final Mono principalMono; + private MockServerWebExchange( MockServerHttpRequest request, @Nullable WebSessionManager sessionManager, - @Nullable ApplicationContext applicationContext) { + @Nullable ApplicationContext applicationContext, @Nullable Principal principal) { super(request, new MockServerHttpResponse(), sessionManager != null ? sessionManager : new DefaultWebSessionManager(), ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver(), applicationContext); + + this.principalMono = (principal != null) ? Mono.just(principal) : Mono.empty(); } @@ -57,6 +63,16 @@ public MockServerHttpResponse getResponse() { return (MockServerHttpResponse) super.getResponse(); } + /** + * Return the user set via {@link Builder#principal(Principal)}. + * @since 6.2.7 + */ + @SuppressWarnings("unchecked") + @Override + public Mono getPrincipal() { + return (Mono) this.principalMono; + } + /** * Create a {@link MockServerWebExchange} from the given mock request. @@ -111,6 +127,9 @@ public static class Builder { @Nullable private ApplicationContext applicationContext; + @Nullable + private Principal principal; + public Builder(MockServerHttpRequest request) { this.request = request; } @@ -147,11 +166,22 @@ public Builder applicationContext(ApplicationContext applicationContext) { return this; } + /** + * Provide a user to associate with the exchange. + * @param principal the principal to use + * @since 6.2.7 + */ + public Builder principal(@Nullable Principal principal) { + this.principal = principal; + return this; + } + /** * Build the {@code MockServerWebExchange} instance. */ public MockServerWebExchange build() { - return new MockServerWebExchange(this.request, this.sessionManager, this.applicationContext); + return new MockServerWebExchange( + this.request, this.sessionManager, this.applicationContext, this.principal); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java index 953aa1094ff0..490416d4b9cb 100644 --- a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java +++ b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java @@ -314,8 +314,7 @@ else if (logger.isWarnEnabled()) { ClassName processAheadOfTime(MergedContextConfiguration mergedConfig, GenerationContext generationContext) throws TestContextAotException { - GenericApplicationContext gac = loadContextForAotProcessing(mergedConfig); - try { + try (GenericApplicationContext gac = loadContextForAotProcessing(mergedConfig)) { return this.aotGenerator.processAheadOfTime(gac, generationContext); } catch (Throwable ex) { @@ -333,7 +332,7 @@ ClassName processAheadOfTime(MergedContextConfiguration mergedConfig, * context or if one of the prerequisites is not met * @see AotContextLoader#loadContextForAotProcessing(MergedContextConfiguration, RuntimeHints) */ - private GenericApplicationContext loadContextForAotProcessing( + GenericApplicationContext loadContextForAotProcessing( MergedContextConfiguration mergedConfig) throws TestContextAotException { Class testClass = mergedConfig.getTestClass(); diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideHandler.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideHandler.java index 4fb5b3c2704f..63a34510033f 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideHandler.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideHandler.java @@ -184,30 +184,32 @@ private static List findHandlers(Class testClass, boolea * @param testClass the original test class * @param handlers the list of handlers found * @param localFieldsOnly whether to search only on local fields within the type hierarchy - * @param visitedEnclosingClasses the set of enclosing classes already visited + * @param visitedTypes the set of types already visited * @since 6.2.2 */ private static void findHandlers(Class clazz, Class testClass, List handlers, - boolean localFieldsOnly, Set> visitedEnclosingClasses) { + boolean localFieldsOnly, Set> visitedTypes) { + + // 0) Ensure that we do not process the same class or interface multiple times. + if (!visitedTypes.add(clazz)) { + return; + } // 1) Search enclosing class hierarchy. if (!localFieldsOnly && TestContextAnnotationUtils.searchEnclosingClass(clazz)) { - Class enclosingClass = clazz.getEnclosingClass(); - if (visitedEnclosingClasses.add(enclosingClass)) { - findHandlers(enclosingClass, testClass, handlers, localFieldsOnly, visitedEnclosingClasses); - } + findHandlers(clazz.getEnclosingClass(), testClass, handlers, localFieldsOnly, visitedTypes); } // 2) Search class hierarchy. Class superclass = clazz.getSuperclass(); if (superclass != null && superclass != Object.class) { - findHandlers(superclass, testClass, handlers, localFieldsOnly, visitedEnclosingClasses); + findHandlers(superclass, testClass, handlers, localFieldsOnly, visitedTypes); } if (!localFieldsOnly) { // 3) Search interfaces. for (Class ifc : clazz.getInterfaces()) { - findHandlers(ifc, testClass, handlers, localFieldsOnly, visitedEnclosingClasses); + findHandlers(ifc, testClass, handlers, localFieldsOnly, visitedTypes); } // 4) Process current class. diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextHierarchyDirtiesContextTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextHierarchyDirtiesContextTests.java index 41388fc7da2c..57d6ad93f3db 100644 --- a/spring-test/src/test/java/org/springframework/test/context/ContextHierarchyDirtiesContextTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/ContextHierarchyDirtiesContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 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. @@ -20,9 +20,8 @@ import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; -import org.junit.runner.JUnitCore; -import org.junit.runner.Result; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.testkit.engine.EngineTestKit; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; @@ -32,9 +31,10 @@ import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.HierarchyMode; -import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; /** * Integration tests that verify proper behavior of {@link DirtiesContext @DirtiesContext} @@ -87,9 +87,11 @@ void methodLevelDirtiesContextWithExhaustiveHierarchyMode() { private void runTestAndVerifyHierarchies(Class testClass, boolean isFooContextActive, boolean isBarContextActive, boolean isBazContextActive) { - JUnitCore jUnitCore = new JUnitCore(); - Result result = jUnitCore.run(testClass); - assertThat(result.wasSuccessful()).as("all tests passed").isTrue(); + EngineTestKit.engine("junit-jupiter") + .selectors(selectClass(testClass)) + .execute() + .testEvents() + .assertStatistics(stats -> stats.started(1).succeeded(1).failed(0)); assertThat(ContextHierarchyDirtiesContextTests.context).isNotNull(); @@ -111,7 +113,7 @@ private void runTestAndVerifyHierarchies(Class testClass, // ------------------------------------------------------------------------- - @RunWith(SpringRunner.class) + @ExtendWith(SpringExtension.class) @ContextHierarchy(@ContextConfiguration(name = "foo")) abstract static class FooTestCase implements ApplicationContextAware { @@ -170,10 +172,10 @@ String bean() { * context. */ @DirtiesContext - public static class ClassLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase { + static class ClassLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase { - @org.junit.Test - public void test() { + @Test + void test() { } } @@ -184,10 +186,10 @@ public void test() { * beginning from the current context hierarchy and down through all subhierarchies. */ @DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL) - public static class ClassLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase { + static class ClassLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase { - @org.junit.Test - public void test() { + @Test + void test() { } } @@ -199,11 +201,11 @@ public void test() { * parent context, and then back down through all subhierarchies of the parent * context. */ - public static class MethodLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase { + static class MethodLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase { - @org.junit.Test + @Test @DirtiesContext - public void test() { + void test() { } } @@ -213,11 +215,11 @@ public void test() { *

After running this test class, the context cache should be cleared * beginning from the current context hierarchy and down through all subhierarchies. */ - public static class MethodLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase { + static class MethodLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase { - @org.junit.Test + @Test @DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL) - public void test() { + void test() { } } diff --git a/spring-test/src/test/java/org/springframework/test/context/SpringTestContextFrameworkTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/SpringTestContextFrameworkTestSuite.java index 4500d5566899..cb4b809abffe 100644 --- a/spring-test/src/test/java/org/springframework/test/context/SpringTestContextFrameworkTestSuite.java +++ b/spring-test/src/test/java/org/springframework/test/context/SpringTestContextFrameworkTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2025 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,8 @@ package org.springframework.test.context; +import org.junit.jupiter.api.ClassOrderer; +import org.junit.platform.suite.api.ConfigurationParameter; import org.junit.platform.suite.api.ExcludeTags; import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.SelectPackages; @@ -44,5 +46,9 @@ @SelectPackages("org.springframework.test.context") @IncludeClassNamePatterns(".*Tests?$") @ExcludeTags("failing-test-case") +@ConfigurationParameter( + key = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME, + value = "org.junit.jupiter.api.ClassOrderer$ClassName" + ) class SpringTestContextFrameworkTestSuite { } diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java index 9961702eba7c..aa9eae8800ce 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java @@ -167,7 +167,6 @@ void endToEndTestsForBeanOverrides() { void endToEndTestsForSelectedTestClasses() { List> testClasses = List.of( org.springframework.test.context.bean.override.easymock.EasyMockBeanIntegrationTests.class, - org.springframework.test.context.junit4.SpringJUnit4ClassRunnerAppCtxTests.class, org.springframework.test.context.junit4.ParameterizedDependencyInjectionTests.class ); diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java index 2e9dd795cf4e..de275583b88b 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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,13 +16,23 @@ package org.springframework.test.context.aot; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.aot.generate.GeneratedFiles; +import org.springframework.aot.generate.InMemoryGeneratedFiles; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.SpringProperties; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.context.aot.TestContextAotGenerator.FAIL_ON_ERROR_PROPERTY_NAME; @@ -60,9 +70,55 @@ void failOnErrorDisabledViaSpringProperty(String value) { assertThat(createGenerator().failOnError).isFalse(); } + @Test // gh-34841 + void contextIsClosedAfterAotProcessing() { + DemoTestContextAotGenerator generator = createGenerator(); + generator.processAheadOfTime(Stream.of(TestCase1.class, TestCase2.class)); + + assertThat(generator.contexts) + .allSatisfy(context -> assertThat(context.isClosed()).as("context is closed").isTrue()); + } + + + private static DemoTestContextAotGenerator createGenerator() { + return new DemoTestContextAotGenerator(new InMemoryGeneratedFiles()); + } + + + private static class DemoTestContextAotGenerator extends TestContextAotGenerator { + + List contexts = new ArrayList<>(); + + DemoTestContextAotGenerator(GeneratedFiles generatedFiles) { + super(generatedFiles); + } + + @Override + GenericApplicationContext loadContextForAotProcessing( + MergedContextConfiguration mergedConfig) throws TestContextAotException { + + GenericApplicationContext context = super.loadContextForAotProcessing(mergedConfig); + this.contexts.add(context); + return context; + } + } + + @SpringJUnitConfig + private static class TestCase1 { + + @Configuration(proxyBeanMethods = false) + static class Config { + // no beans + } + } + + @SpringJUnitConfig + private static class TestCase2 { - private static TestContextAotGenerator createGenerator() { - return new TestContextAotGenerator(null); + @Configuration(proxyBeanMethods = false) + static class Config { + // no beans + } } } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/AbstractMockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/AbstractMockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests.java new file mode 100644 index 000000000000..84fd8be1433b --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/AbstractMockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2025 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.bean.override.mockito; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.bean.override.example.ExampleService; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.mockito.MockitoAssertions.assertIsMock; + +/** + * Abstract top-level class and abstract inner class for integration tests for + * {@link MockitoBean @MockitoBean} which verify that {@code @MockitoBean} fields + * are not discovered more than once when searching intertwined enclosing class + * hierarchies and type hierarchies, when a superclass is present twice + * in the intertwined hierarchies. + * + * @author Sam Brannen + * @since 6.2.7 + * @see MockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests + * @see gh-34844 + */ +@ExtendWith(SpringExtension.class) +abstract class AbstractMockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests { + + @Autowired + ApplicationContext enclosingContext; + + @MockitoBean + ExampleService service; + + + @Test + void topLevelTest() { + assertIsMock(service); + assertThat(enclosingContext.getBeanNamesForType(ExampleService.class)).hasSize(1); + } + + + abstract class AbstractBaseClassForNestedTests { + + @Test + void nestedTest(ApplicationContext nestedContext) { + assertIsMock(service); + assertThat(enclosingContext).isSameAs(nestedContext); + assertThat(enclosingContext.getBeanNamesForType(ExampleService.class)).hasSize(1); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanNestedAndTypeHierarchiesTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests.java similarity index 81% rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanNestedAndTypeHierarchiesTests.java rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests.java index 72babe686745..7934e07f7bdc 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanNestedAndTypeHierarchiesTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests.java @@ -31,14 +31,17 @@ /** * Integration tests for {@link MockitoBean @MockitoBean} which verify that * {@code @MockitoBean} fields are not discovered more than once when searching - * intertwined enclosing class hierarchies and type hierarchies. + * intertwined enclosing class hierarchies and type hierarchies, when an enclosing + * class is present twice in the intertwined hierarchies. * * @author Sam Brannen * @since 6.2.3 + * @see MockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests + * @see MockitoBeanWithInterfacePresentTwiceTests * @see gh-34324 */ @ExtendWith(SpringExtension.class) -class MockitoBeanNestedAndTypeHierarchiesTests { +class MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests { @Autowired ApplicationContext enclosingContext; @@ -50,6 +53,7 @@ class MockitoBeanNestedAndTypeHierarchiesTests { @Test void topLevelTest() { assertIsMock(service); + assertThat(enclosingContext.getBeanNamesForType(ExampleService.class)).hasSize(1); // The following are prerequisites for the reported regression. assertThat(NestedTests.class.getSuperclass()) @@ -66,6 +70,7 @@ abstract class AbstractBaseClassForNestedTests { void nestedTest(ApplicationContext nestedContext) { assertIsMock(service); assertThat(enclosingContext).isSameAs(nestedContext); + assertThat(enclosingContext.getBeanNamesForType(ExampleService.class)).hasSize(1); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests.java new file mode 100644 index 000000000000..bc43837b2441 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2025 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.bean.override.mockito; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link MockitoBean @MockitoBean} which verify that + * {@code @MockitoBean} fields are not discovered more than once when searching + * intertwined enclosing class hierarchies and type hierarchies, when a superclass + * is present twice in the intertwined hierarchies. + * + * @author Sam Brannen + * @since 6.2.7 + * @see MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests + * @see MockitoBeanWithInterfacePresentTwiceTests + * @see gh-34844 + */ +class MockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests + extends AbstractMockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests { + + @Test + @Override + void topLevelTest() { + super.topLevelTest(); + + // The following are prerequisites for the reported regression. + assertThat(NestedTests.class.getSuperclass()) + .isEqualTo(AbstractBaseClassForNestedTests.class); + assertThat(NestedTests.class.getEnclosingClass()) + .isEqualTo(getClass()); + assertThat(NestedTests.class.getEnclosingClass().getSuperclass()) + .isEqualTo(AbstractBaseClassForNestedTests.class.getEnclosingClass()) + .isEqualTo(getClass().getSuperclass()); + } + + + @Nested + class NestedTests extends AbstractBaseClassForNestedTests { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanWithInterfacePresentTwiceTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanWithInterfacePresentTwiceTests.java new file mode 100644 index 000000000000..95d4ac845858 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanWithInterfacePresentTwiceTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2025 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.bean.override.mockito; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.bean.override.example.ExampleService; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.mockito.MockitoAssertions.assertIsMock; + +/** + * Integration tests for {@link MockitoBean @MockitoBean} which verify that type-level + * {@code @MockitoBean} declarations are not discovered more than once when searching + * a type hierarchy, when an interface is present twice in the hierarchy. + * + * @author Sam Brannen + * @since 6.2.7 + * @see MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests + * @see MockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests + * @see gh-34844 + */ +class MockitoBeanWithInterfacePresentTwiceTests extends AbstractMockitoBeanWithInterfacePresentTwiceTests + implements MockConfigInterface { + + @Test + void test(ApplicationContext context) { + assertIsMock(service); + assertThat(context.getBeanNamesForType(ExampleService.class)).hasSize(1); + + // The following are prerequisites for the tested scenario. + assertThat(getClass().getInterfaces()).containsExactly(MockConfigInterface.class); + assertThat(getClass().getSuperclass().getInterfaces()).containsExactly(MockConfigInterface.class); + } + +} + +@MockitoBean(types = ExampleService.class) +interface MockConfigInterface { +} + +@ExtendWith(SpringExtension.class) +abstract class AbstractMockitoBeanWithInterfacePresentTwiceTests implements MockConfigInterface { + + @Autowired + ExampleService service; + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTestNGTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTestNGTests.java index f0663a50e2c0..033eb1b0270d 100644 --- a/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTestNGTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTestNGTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -31,6 +31,7 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; +import org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; import org.springframework.test.context.testng.TrackingTestNGTestListener; @@ -162,12 +163,18 @@ static void verifyFinalCacheState() { // ------------------------------------------------------------------- - @TestExecutionListeners(listeners = { DependencyInjectionTestExecutionListener.class, - DirtiesContextTestExecutionListener.class }, inheritListeners = false) @ContextConfiguration + // Ensure that we do not include the EventPublishingTestExecutionListener + // since it will access the ApplicationContext for each method in the + // TestExecutionListener API, thus distorting our cache hit/miss results. + @TestExecutionListeners({ + DirtiesContextBeforeModesTestExecutionListener.class, + DependencyInjectionTestExecutionListener.class, + DirtiesContextTestExecutionListener.class + }) abstract static class BaseTestCase extends AbstractTestNGSpringContextTests { - @Configuration + @Configuration(proxyBeanMethods = false) static class Config { /* no beans */ } diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTests.java index a5527be66e0b..1ed24d6a84f1 100644 --- a/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 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. @@ -21,7 +21,8 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.testkit.engine.EngineTestKit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -31,15 +32,14 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.springframework.test.context.cache.ContextCacheTestUtils.assertContextCacheStatistics; import static org.springframework.test.context.cache.ContextCacheTestUtils.resetContextCache; -import static org.springframework.test.context.junit4.JUnitTestingUtils.runTestsAndAssertCounters; /** * JUnit based integration test which verifies correct {@linkplain ContextCache @@ -131,15 +131,24 @@ void verifyDirtiesContextBehavior() throws Exception { 0, cacheHits.incrementAndGet(), cacheMisses.get()); } - private void runTestClassAndAssertStats(Class testClass, int expectedTestCount) throws Exception { - runTestsAndAssertCounters(testClass, expectedTestCount, 0, expectedTestCount, 0, 0); - } - private void assertBehaviorForCleanTestCase() throws Exception { runTestClassAndAssertStats(CleanTestCase.class, 1); assertContextCacheStatistics("after clean test class", 1, cacheHits.get(), cacheMisses.incrementAndGet()); } + private void runTestClassAndAssertStats(Class testClass, int expectedTestCount) throws Exception { + EngineTestKit.engine("junit-jupiter") + .selectors(selectClass(testClass)) + .execute() + .testEvents() + .assertStatistics(stats -> stats + .started(expectedTestCount) + .finished(expectedTestCount) + .succeeded(expectedTestCount) + .failed(0) + .aborted(0)); + } + @AfterAll static void verifyFinalCacheState() { assertContextCacheStatistics("AfterClass", 0, cacheHits.get(), cacheMisses.get()); @@ -148,7 +157,7 @@ static void verifyFinalCacheState() { // ------------------------------------------------------------------- - @RunWith(SpringRunner.class) + @ExtendWith(SpringExtension.class) @ContextConfiguration // Ensure that we do not include the EventPublishingTestExecutionListener // since it will access the ApplicationContext for each method in the @@ -160,7 +169,7 @@ static void verifyFinalCacheState() { }) abstract static class BaseTestCase { - @Configuration + @Configuration(proxyBeanMethods = false) static class Config { /* no beans */ } @@ -175,75 +184,75 @@ protected void assertApplicationContextWasAutowired() { } } - public static final class CleanTestCase extends BaseTestCase { + static final class CleanTestCase extends BaseTestCase { - @org.junit.Test - public void verifyContextWasAutowired() { + @Test + void verifyContextWasAutowired() { assertApplicationContextWasAutowired(); } } @DirtiesContext - public static class ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends BaseTestCase { + static class ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends BaseTestCase { - @org.junit.Test - public void verifyContextWasAutowired() { + @Test + void verifyContextWasAutowired() { assertApplicationContextWasAutowired(); } } - public static class InheritedClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends + static class InheritedClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase { } @DirtiesContext(classMode = ClassMode.AFTER_CLASS) - public static class ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends BaseTestCase { + static class ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends BaseTestCase { - @org.junit.Test - public void verifyContextWasAutowired() { + @Test + void verifyContextWasAutowired() { assertApplicationContextWasAutowired(); } } - public static class InheritedClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends + static class InheritedClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase { } @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) - public static class ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends BaseTestCase { + static class ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends BaseTestCase { - @org.junit.Test - public void verifyContextWasAutowired1() { + @Test + void verifyContextWasAutowired1() { assertApplicationContextWasAutowired(); } - @org.junit.Test - public void verifyContextWasAutowired2() { + @Test + void verifyContextWasAutowired2() { assertApplicationContextWasAutowired(); } - @org.junit.Test - public void verifyContextWasAutowired3() { + @Test + void verifyContextWasAutowired3() { assertApplicationContextWasAutowired(); } } - public static class InheritedClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends + static class InheritedClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase { } @DirtiesContext - public static class ClassLevelDirtiesContextWithDirtyMethodsTestCase extends BaseTestCase { + static class ClassLevelDirtiesContextWithDirtyMethodsTestCase extends BaseTestCase { - @org.junit.Test + @Test @DirtiesContext - public void dirtyContext() { + void dirtyContext() { assertApplicationContextWasAutowired(); } } - public static class InheritedClassLevelDirtiesContextWithDirtyMethodsTestCase extends + static class InheritedClassLevelDirtiesContextWithDirtyMethodsTestCase extends ClassLevelDirtiesContextWithDirtyMethodsTestCase { } diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/MethodLevelDirtiesContextTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/MethodLevelDirtiesContextTests.java index 72dbddc0eaf5..323d7eb6d329 100644 --- a/spring-test/src/test/java/org/springframework/test/context/cache/MethodLevelDirtiesContextTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/cache/MethodLevelDirtiesContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -37,7 +37,7 @@ import static org.springframework.test.annotation.DirtiesContext.MethodMode.BEFORE_METHOD; /** - * Integration test which verifies correct interaction between the + * Integration tests which verify correct interaction between the * {@link DirtiesContextBeforeModesTestExecutionListener}, * {@link DependencyInjectionTestExecutionListener}, and * {@link DirtiesContextTestExecutionListener} when diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/SpringExtensionContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/SpringExtensionContextCacheTests.java index 1e67f031453b..2c0861c8a26a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/cache/SpringExtensionContextCacheTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/cache/SpringExtensionContextCacheTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -37,10 +37,9 @@ import static org.springframework.test.context.cache.ContextCacheTestUtils.resetContextCache; /** - * Unit tests which verify correct {@link ContextCache - * application context caching} in conjunction with the - * {@link SpringExtension} and the {@link DirtiesContext - * @DirtiesContext} annotation at the method level. + * JUnit based integration test which verifies correct {@linkplain ContextCache + * application context caching} in conjunction with the {@link SpringExtension} and + * {@link DirtiesContext @DirtiesContext} at the method level. * * @author Sam Brannen * @author Juergen Hoeller @@ -48,7 +47,7 @@ * @see ContextCacheTests * @see LruContextCacheTests */ -@SpringJUnitConfig(locations = "../junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml") +@SpringJUnitConfig(locations = "../config/CoreContextConfigurationAppCtxTests-context.xml") @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class }) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class SpringExtensionContextCacheTests { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/AbsolutePathSpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/config/AbsolutePathContextConfigurationAppCtxTests.java similarity index 62% rename from spring-test/src/test/java/org/springframework/test/context/junit4/AbsolutePathSpringJUnit4ClassRunnerAppCtxTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/AbsolutePathContextConfigurationAppCtxTests.java index 56caa8c9034e..4cc1559eaff0 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/AbsolutePathSpringJUnit4ClassRunnerAppCtxTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/AbsolutePathContextConfigurationAppCtxTests.java @@ -14,22 +14,22 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.config; import org.springframework.test.context.ContextConfiguration; /** - * Extension of {@link SpringJUnit4ClassRunnerAppCtxTests}, which verifies that + * Extension of {@link CoreContextConfigurationAppCtxTests}, which verifies that * we can specify an explicit, absolute path location for our * application context. * * @author Sam Brannen * @since 2.5 - * @see SpringJUnit4ClassRunnerAppCtxTests - * @see ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests - * @see RelativePathSpringJUnit4ClassRunnerAppCtxTests + * @see CoreContextConfigurationAppCtxTests + * @see ClassPathResourceContextConfigurationAppCtxTests + * @see RelativePathContextConfigurationAppCtxTests */ -@ContextConfiguration(locations = { SpringJUnit4ClassRunnerAppCtxTests.DEFAULT_CONTEXT_RESOURCE_PATH }, inheritLocations = false) -public class AbsolutePathSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests { +@ContextConfiguration(locations = CoreContextConfigurationAppCtxTests.DEFAULT_CONTEXT_RESOURCE_PATH, inheritLocations = false) +class AbsolutePathContextConfigurationAppCtxTests extends CoreContextConfigurationAppCtxTests { /* all tests are in the parent class. */ } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/config/AnnotationConfigContextConfigurationAppCtxTests.java similarity index 71% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/AnnotationConfigContextConfigurationAppCtxTests.java index cc368b2862b2..e55e6840efd6 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/AnnotationConfigContextConfigurationAppCtxTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2025 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. @@ -14,19 +14,18 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation; +package org.springframework.test.context.config; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunnerAppCtxTests; /** * Integration tests that verify support for configuration classes in * the Spring TestContext Framework. * - *

Furthermore, by extending {@link SpringJUnit4ClassRunnerAppCtxTests}, + *

Furthermore, by extending {@link CoreContextConfigurationAppCtxTests}, * this class also verifies support for several basic features of the * Spring TestContext Framework. See JavaDoc in - * {@code SpringJUnit4ClassRunnerAppCtxTests} for details. + * {@link CoreContextConfigurationAppCtxTests} for details. * *

Configuration will be loaded from {@link PojoAndStringConfig}. * @@ -34,6 +33,6 @@ * @since 3.1 */ @ContextConfiguration(classes = PojoAndStringConfig.class, inheritLocations = false) -public class AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests { +class AnnotationConfigContextConfigurationAppCtxTests extends CoreContextConfigurationAppCtxTests { /* all tests are in the parent class. */ } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr6128/AutowiredQualifierTests.java b/spring-test/src/test/java/org/springframework/test/context/config/AutowiredQualifierTests.java similarity index 56% rename from spring-test/src/test/java/org/springframework/test/context/junit4/spr6128/AutowiredQualifierTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/AutowiredQualifierTests.java index 8436a50c95d8..d46cda1c463d 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr6128/AutowiredQualifierTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/AutowiredQualifierTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,43 +14,59 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.spr6128; +package org.springframework.test.context.config; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; /** - * Integration tests to verify claims made in SPR-6128. + * Integration tests to verify claims made in + * gh-10796. * * @author Sam Brannen * @author Chris Beams * @since 3.0 */ +@ExtendWith(SpringExtension.class) @ContextConfiguration -@RunWith(SpringJUnit4ClassRunner.class) -public class AutowiredQualifierTests { +class AutowiredQualifierTests { @Autowired - private String foo; + String foo; @Autowired @Qualifier("customFoo") - private String customFoo; + String customFoo; @Test - public void test() { + void test() { assertThat(foo).isEqualTo("normal"); assertThat(customFoo).isEqualTo("custom"); } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + String foo() { + return "normal"; + } + + @Bean + String customFoo() { + return "custom"; + } + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/BeanOverridingDefaultConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/config/BeanOverridingDefaultConfigClassesInheritedTests.java similarity index 82% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/BeanOverridingDefaultConfigClassesInheritedTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/BeanOverridingDefaultConfigClassesInheritedTests.java index 9e0b2378dd97..f9b1b3db0c03 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/BeanOverridingDefaultConfigClassesInheritedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/BeanOverridingDefaultConfigClassesInheritedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation; +package org.springframework.test.context.config; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.beans.testfixture.beans.Employee; import org.springframework.context.annotation.Bean; @@ -36,13 +36,21 @@ * @since 3.1 */ @ContextConfiguration -public class BeanOverridingDefaultConfigClassesInheritedTests extends DefaultConfigClassesBaseTests { +class BeanOverridingDefaultConfigClassesInheritedTests extends DefaultConfigClassesBaseTests { - @Configuration + @Test + @Override + void verifyEmployeeSetFromBaseContextConfig() { + assertThat(this.employee).as("The employee should have been autowired.").isNotNull(); + assertThat(this.employee.getName()).as("The employee bean should have been overridden.").isEqualTo("Yoda"); + } + + + @Configuration(proxyBeanMethods = false) static class ContextConfiguration { @Bean - public Employee employee() { + Employee employee() { Employee employee = new Employee(); employee.setName("Yoda"); employee.setAge(900); @@ -51,12 +59,4 @@ public Employee employee() { } } - - @Test - @Override - public void verifyEmployeeSetFromBaseContextConfig() { - assertThat(this.employee).as("The employee should have been autowired.").isNotNull(); - assertThat(this.employee.getName()).as("The employee bean should have been overridden.").isEqualTo("Yoda"); - } - } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/BeanOverridingExplicitConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/config/BeanOverridingExplicitConfigClassesInheritedTests.java similarity index 82% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/BeanOverridingExplicitConfigClassesInheritedTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/BeanOverridingExplicitConfigClassesInheritedTests.java index 31fb3d0ecfcf..c2170704e9e5 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/BeanOverridingExplicitConfigClassesInheritedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/BeanOverridingExplicitConfigClassesInheritedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation; +package org.springframework.test.context.config; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.test.context.ContextConfiguration; @@ -33,11 +33,11 @@ * @since 3.1 */ @ContextConfiguration(classes = BeanOverridingDefaultConfigClassesInheritedTests.ContextConfiguration.class) -public class BeanOverridingExplicitConfigClassesInheritedTests extends ExplicitConfigClassesBaseTests { +class BeanOverridingExplicitConfigClassesInheritedTests extends ExplicitConfigClassesBaseTests { @Test @Override - public void verifyEmployeeSetFromBaseContextConfig() { + void verifyEmployeeSetFromBaseContextConfig() { assertThat(this.employee).as("The employee should have been autowired.").isNotNull(); assertThat(this.employee.getName()).as("The employee bean should have been overridden.").isEqualTo("Yoda"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/config/ClassPathResourceContextConfigurationAppCtxTests.java similarity index 55% rename from spring-test/src/test/java/org/springframework/test/context/junit4/ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/ClassPathResourceContextConfigurationAppCtxTests.java index cbdc15cf9cac..df4c3f7aae5f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/ClassPathResourceContextConfigurationAppCtxTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -14,36 +14,35 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.config; import org.springframework.test.context.ContextConfiguration; import org.springframework.util.ResourceUtils; /** - * Extension of {@link SpringJUnit4ClassRunnerAppCtxTests}, which verifies that + * Extension of {@link CoreContextConfigurationAppCtxTests}, which verifies that * we can specify an explicit, classpath location for our application * context. * * @author Sam Brannen * @since 2.5 - * @see SpringJUnit4ClassRunnerAppCtxTests + * @see CoreContextConfigurationAppCtxTests * @see #CLASSPATH_CONTEXT_RESOURCE_PATH - * @see AbsolutePathSpringJUnit4ClassRunnerAppCtxTests - * @see RelativePathSpringJUnit4ClassRunnerAppCtxTests + * @see AbsolutePathContextConfigurationAppCtxTests + * @see RelativePathContextConfigurationAppCtxTests */ -@ContextConfiguration(locations = { ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests.CLASSPATH_CONTEXT_RESOURCE_PATH }, inheritLocations = false) -public class ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests { +@ContextConfiguration(locations = { ClassPathResourceContextConfigurationAppCtxTests.CLASSPATH_CONTEXT_RESOURCE_PATH }, inheritLocations = false) +class ClassPathResourceContextConfigurationAppCtxTests extends CoreContextConfigurationAppCtxTests { /** * Classpath-based resource path for the application context configuration - * for {@link SpringJUnit4ClassRunnerAppCtxTests}: - * {@code "classpath:/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml"} + * for {@link CoreContextConfigurationAppCtxTests}: {@value} * - * @see SpringJUnit4ClassRunnerAppCtxTests#DEFAULT_CONTEXT_RESOURCE_PATH + * @see CoreContextConfigurationAppCtxTests#DEFAULT_CONTEXT_RESOURCE_PATH * @see ResourceUtils#CLASSPATH_URL_PREFIX */ public static final String CLASSPATH_CONTEXT_RESOURCE_PATH = ResourceUtils.CLASSPATH_URL_PREFIX + - SpringJUnit4ClassRunnerAppCtxTests.DEFAULT_CONTEXT_RESOURCE_PATH; + CoreContextConfigurationAppCtxTests.DEFAULT_CONTEXT_RESOURCE_PATH; /* all tests are in the parent class. */ diff --git a/spring-test/src/test/java/org/springframework/test/context/config/ContextConfigTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/config/ContextConfigTestSuite.java new file mode 100644 index 000000000000..485e128b6bc1 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/config/ContextConfigTestSuite.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2025 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.config; + +import org.junit.jupiter.api.ClassOrderer; +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +/** + * JUnit Platform based test suite annotation-driven configuration class + * support in the Spring TestContext Framework. + * + *

This suite is only intended to be used manually within an IDE. + * + *

Logging Configuration

+ * + *

In order for our log4j2 configuration to be used in an IDE, you must + * set the following system property before running any tests — for + * example, in Run Configurations in Eclipse. + * + *

+ * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
+ * 
+ * + * @author Sam Brannen + * @since 3.1 + */ +@Suite +@IncludeEngines("junit-jupiter") +@SelectPackages("org.springframework.test.context.config") +@IncludeClassNamePatterns(".*Tests$") +@ConfigurationParameter( + key = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME, + value = "org.junit.jupiter.api.ClassOrderer$ClassName" +) +public class ContextConfigTestSuite { +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/config/CoreContextConfigurationAppCtxTests.java similarity index 79% rename from spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/CoreContextConfigurationAppCtxTests.java index 4bbc906dd009..4d00fba6a647 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/CoreContextConfigurationAppCtxTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.config; import jakarta.annotation.Resource; import jakarta.inject.Inject; import jakarta.inject.Named; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.InitializingBean; @@ -33,18 +33,18 @@ import org.springframework.context.ApplicationContextAware; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.GenericXmlContextLoader; import static org.assertj.core.api.Assertions.assertThat; /** - * SpringJUnit4ClassRunnerAppCtxTests serves as a proof of concept - * JUnit 4 based test class, which verifies the expected functionality of - * {@link SpringRunner} in conjunction with the following: + * {@code CoreContextConfigurationAppCtxTests} serves as a core test class, which + * verifies the expected functionality of {@link ContextConfiguration @ContextConfiguration} + * in conjunction with the following: * *
    - *
  • {@link ContextConfiguration @ContextConfiguration}
  • *
  • {@link Autowired @Autowired}
  • *
  • {@link Qualifier @Qualifier}
  • *
  • {@link Resource @Resource}
  • @@ -67,21 +67,24 @@ * * @author Sam Brannen * @since 2.5 - * @see AbsolutePathSpringJUnit4ClassRunnerAppCtxTests - * @see RelativePathSpringJUnit4ClassRunnerAppCtxTests - * @see InheritedConfigSpringJUnit4ClassRunnerAppCtxTests + * @see AbsolutePathContextConfigurationAppCtxTests + * @see AnnotationConfigContextConfigurationAppCtxTests + * @see ClassPathResourceContextConfigurationAppCtxTests + * @see InheritedConfigContextConfigurationAppCtxTests + * @see MultipleResourcesContextConfigurationAppCtxTests + * @see RelativePathContextConfigurationAppCtxTests */ -@RunWith(SpringRunner.class) +@ExtendWith(SpringExtension.class) @ContextConfiguration @TestExecutionListeners(DependencyInjectionTestExecutionListener.class) -public class SpringJUnit4ClassRunnerAppCtxTests implements ApplicationContextAware, BeanNameAware, InitializingBean { +class CoreContextConfigurationAppCtxTests implements ApplicationContextAware, BeanNameAware, InitializingBean { /** * Default resource path for the application context configuration for - * {@link SpringJUnit4ClassRunnerAppCtxTests}: {@value} + * {@link CoreContextConfigurationAppCtxTests}: {@value} */ public static final String DEFAULT_CONTEXT_RESOURCE_PATH = - "/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml"; + "/org/springframework/test/context/config/CoreContextConfigurationAppCtxTests-context.xml"; private Employee employee; @@ -136,12 +139,12 @@ protected void setBar(String bar) { } @Autowired - public void setLiteralParameterValue(@Value("enigma") String literalParameterValue) { + void setLiteralParameterValue(@Value("enigma") String literalParameterValue) { this.literalParameterValue = literalParameterValue; } @Autowired - public void setSpelParameterValue(@Value("#{2 == (1+1)}") Boolean spelParameterValue) { + void setSpelParameterValue(@Value("#{2 == (1+1)}") Boolean spelParameterValue) { this.spelParameterValue = spelParameterValue; } @@ -162,23 +165,23 @@ public void afterPropertiesSet() { @Test - public void verifyBeanNameSet() { + void verifyBeanNameSet() { assertThat(this.beanName).as("The bean name of this test instance should have been set due to BeanNameAware semantics.") .startsWith(getClass().getName()); } @Test - public void verifyApplicationContextSet() { + void verifyApplicationContextSet() { assertThat(this.applicationContext).as("The application context should have been set due to ApplicationContextAware semantics.").isNotNull(); } @Test - public void verifyBeanInitialized() { + void verifyBeanInitialized() { assertThat(this.beanInitialized).as("This test bean should have been initialized due to InitializingBean semantics.").isTrue(); } @Test - public void verifyAnnotationAutowiredAndInjectedFields() { + void verifyAnnotationAutowiredAndInjectedFields() { assertThat(this.nonrequiredLong).as("The nonrequiredLong field should NOT have been autowired.").isNull(); assertThat(this.quux).as("The quux field should have been autowired via @Autowired and @Qualifier.").isEqualTo("Quux"); assertThat(this.namedQuux).as("The namedFoo field should have been injected via @Inject and @Named.").isEqualTo("Quux"); @@ -192,13 +195,13 @@ public void verifyAnnotationAutowiredAndInjectedFields() { } @Test - public void verifyAnnotationAutowiredMethods() { + void verifyAnnotationAutowiredMethods() { assertThat(this.employee).as("The employee setter method should have been autowired.").isNotNull(); assertThat(this.employee.getName()).isEqualTo("John Smith"); } @Test - public void verifyAutowiredAtValueFields() { + void verifyAutowiredAtValueFields() { assertThat(this.literalFieldValue).as("Literal @Value field should have been autowired").isNotNull(); assertThat(this.spelFieldValue).as("SpEL @Value field should have been autowired.").isNotNull(); assertThat(this.literalFieldValue).isEqualTo("enigma"); @@ -206,7 +209,7 @@ public void verifyAutowiredAtValueFields() { } @Test - public void verifyAutowiredAtValueMethods() { + void verifyAutowiredAtValueMethods() { assertThat(this.literalParameterValue).as("Literal @Value method parameter should have been autowired.").isNotNull(); assertThat(this.spelParameterValue).as("SpEL @Value method parameter should have been autowired.").isNotNull(); assertThat(this.literalParameterValue).isEqualTo("enigma"); @@ -214,12 +217,12 @@ public void verifyAutowiredAtValueMethods() { } @Test - public void verifyResourceAnnotationInjectedFields() { + void verifyResourceAnnotationInjectedFields() { assertThat(this.foo).as("The foo field should have been injected via @Resource.").isEqualTo("Foo"); } @Test - public void verifyResourceAnnotationInjectedMethods() { + void verifyResourceAnnotationInjectedMethods() { assertThat(this.bar).as("The bar method should have been wired via @Resource.").isEqualTo("Bar"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultConfigClassesBaseTests.java b/spring-test/src/test/java/org/springframework/test/context/config/DefaultConfigClassesBaseTests.java similarity index 73% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultConfigClassesBaseTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/DefaultConfigClassesBaseTests.java index 557e37eaeeae..db306209a0cc 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultConfigClassesBaseTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/DefaultConfigClassesBaseTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,17 +14,15 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation; +package org.springframework.test.context.config; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.testfixture.beans.Employee; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.support.AnnotationConfigContextLoader; import static org.assertj.core.api.Assertions.assertThat; @@ -39,15 +37,25 @@ * @since 3.1 * @see DefaultLoaderDefaultConfigClassesBaseTests */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(loader = AnnotationConfigContextLoader.class) -public class DefaultConfigClassesBaseTests { +@SpringJUnitConfig(loader = AnnotationConfigContextLoader.class) +class DefaultConfigClassesBaseTests { - @Configuration + @Autowired + Employee employee; + + + @Test + void verifyEmployeeSetFromBaseContextConfig() { + assertThat(this.employee).as("The employee field should have been autowired.").isNotNull(); + assertThat(this.employee.getName()).isEqualTo("John Smith"); + } + + + @Configuration(proxyBeanMethods = false) static class ContextConfiguration { @Bean - public Employee employee() { + Employee employee() { Employee employee = new Employee(); employee.setName("John Smith"); employee.setAge(42); @@ -56,15 +64,4 @@ public Employee employee() { } } - - @Autowired - protected Employee employee; - - - @Test - public void verifyEmployeeSetFromBaseContextConfig() { - assertThat(this.employee).as("The employee field should have been autowired.").isNotNull(); - assertThat(this.employee.getName()).isEqualTo("John Smith"); - } - } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/config/DefaultConfigClassesInheritedTests.java similarity index 81% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultConfigClassesInheritedTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/DefaultConfigClassesInheritedTests.java index 9604c8f520f3..83b3d3322719 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultConfigClassesInheritedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/DefaultConfigClassesInheritedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation; +package org.springframework.test.context.config; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.testfixture.beans.Pet; @@ -37,26 +37,26 @@ * @since 3.1 */ @ContextConfiguration -public class DefaultConfigClassesInheritedTests extends DefaultConfigClassesBaseTests { - - @Configuration - static class ContextConfiguration { - - @Bean - public Pet pet() { - return new Pet("Fido"); - } - } - +class DefaultConfigClassesInheritedTests extends DefaultConfigClassesBaseTests { @Autowired - private Pet pet; + Pet pet; @Test - public void verifyPetSetFromExtendedContextConfig() { + void verifyPetSetFromExtendedContextConfig() { assertThat(this.pet).as("The pet should have been autowired.").isNotNull(); assertThat(this.pet.getName()).isEqualTo("Fido"); } + + @Configuration(proxyBeanMethods = false) + static class ContextConfiguration { + + @Bean + Pet pet() { + return new Pet("Fido"); + } + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.java similarity index 83% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.java index e530a99ee48b..9f46b13a1efb 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation; +package org.springframework.test.context.config; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.beans.testfixture.beans.Employee; import org.springframework.context.annotation.Bean; @@ -35,14 +35,22 @@ * @since 3.1 */ @ContextConfiguration -public class DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests extends +class DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests extends DefaultLoaderDefaultConfigClassesBaseTests { - @Configuration + @Test + @Override + void verifyEmployeeSetFromBaseContextConfig() { + assertThat(this.employee).as("The employee should have been autowired.").isNotNull(); + assertThat(this.employee.getName()).as("The employee bean should have been overridden.").isEqualTo("Yoda"); + } + + + @Configuration(proxyBeanMethods = false) static class Config { @Bean - public Employee employee() { + Employee employee() { Employee employee = new Employee(); employee.setName("Yoda"); employee.setAge(900); @@ -51,12 +59,4 @@ public Employee employee() { } } - - @Test - @Override - public void verifyEmployeeSetFromBaseContextConfig() { - assertThat(this.employee).as("The employee should have been autowired.").isNotNull(); - assertThat(this.employee.getName()).as("The employee bean should have been overridden.").isEqualTo("Yoda"); - } - } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.java similarity index 83% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.java index 4be240ce7e37..37f4d7d00c84 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation; +package org.springframework.test.context.config; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.support.DelegatingSmartContextLoader; @@ -32,12 +32,12 @@ * @since 3.1 */ @ContextConfiguration(classes = DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.Config.class) -public class DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests extends +class DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests extends DefaultLoaderExplicitConfigClassesBaseTests { @Test @Override - public void verifyEmployeeSetFromBaseContextConfig() { + void verifyEmployeeSetFromBaseContextConfig() { assertThat(this.employee).as("The employee should have been autowired.").isNotNull(); assertThat(this.employee.getName()).as("The employee bean should have been overridden.").isEqualTo("Yoda"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderDefaultConfigClassesBaseTests.java b/spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderDefaultConfigClassesBaseTests.java similarity index 74% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderDefaultConfigClassesBaseTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderDefaultConfigClassesBaseTests.java index b032b6e4bca2..1ef7a9f0fc9a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderDefaultConfigClassesBaseTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderDefaultConfigClassesBaseTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,17 +14,15 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation; +package org.springframework.test.context.config; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.testfixture.beans.Employee; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.support.DelegatingSmartContextLoader; import static org.assertj.core.api.Assertions.assertThat; @@ -38,15 +36,25 @@ * @since 3.1 * @see DefaultConfigClassesBaseTests */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration -public class DefaultLoaderDefaultConfigClassesBaseTests { +@SpringJUnitConfig +class DefaultLoaderDefaultConfigClassesBaseTests { - @Configuration + @Autowired + Employee employee; + + + @Test + void verifyEmployeeSetFromBaseContextConfig() { + assertThat(this.employee).as("The employee field should have been autowired.").isNotNull(); + assertThat(this.employee.getName()).isEqualTo("John Smith"); + } + + + @Configuration(proxyBeanMethods = false) static class Config { @Bean - public Employee employee() { + Employee employee() { Employee employee = new Employee(); employee.setName("John Smith"); employee.setAge(42); @@ -55,15 +63,4 @@ public Employee employee() { } } - - @Autowired - protected Employee employee; - - - @Test - public void verifyEmployeeSetFromBaseContextConfig() { - assertThat(this.employee).as("The employee field should have been autowired.").isNotNull(); - assertThat(this.employee.getName()).isEqualTo("John Smith"); - } - } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderDefaultConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderDefaultConfigClassesInheritedTests.java similarity index 80% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderDefaultConfigClassesInheritedTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderDefaultConfigClassesInheritedTests.java index 780791bdc441..ca011fa18bc4 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderDefaultConfigClassesInheritedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderDefaultConfigClassesInheritedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation; +package org.springframework.test.context.config; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.testfixture.beans.Pet; @@ -36,26 +36,26 @@ * @since 3.1 */ @ContextConfiguration -public class DefaultLoaderDefaultConfigClassesInheritedTests extends DefaultLoaderDefaultConfigClassesBaseTests { - - @Configuration - static class Config { - - @Bean - public Pet pet() { - return new Pet("Fido"); - } - } - +class DefaultLoaderDefaultConfigClassesInheritedTests extends DefaultLoaderDefaultConfigClassesBaseTests { @Autowired - private Pet pet; + Pet pet; @Test - public void verifyPetSetFromExtendedContextConfig() { + void verifyPetSetFromExtendedContextConfig() { assertThat(this.pet).as("The pet should have been autowired.").isNotNull(); assertThat(this.pet.getName()).isEqualTo("Fido"); } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + Pet pet() { + return new Pet("Fido"); + } + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderExplicitConfigClassesBaseTests.java b/spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderExplicitConfigClassesBaseTests.java similarity index 67% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderExplicitConfigClassesBaseTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderExplicitConfigClassesBaseTests.java index aa0d8ab54a1d..13569079c74c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderExplicitConfigClassesBaseTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderExplicitConfigClassesBaseTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,15 +14,13 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation; +package org.springframework.test.context.config; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.testfixture.beans.Employee; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.support.DelegatingSmartContextLoader; import static org.assertj.core.api.Assertions.assertThat; @@ -35,16 +33,15 @@ * @author Sam Brannen * @since 3.1 */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = DefaultLoaderDefaultConfigClassesBaseTests.Config.class) -public class DefaultLoaderExplicitConfigClassesBaseTests { +@SpringJUnitConfig(DefaultLoaderDefaultConfigClassesBaseTests.Config.class) +class DefaultLoaderExplicitConfigClassesBaseTests { @Autowired - protected Employee employee; + Employee employee; @Test - public void verifyEmployeeSetFromBaseContextConfig() { + void verifyEmployeeSetFromBaseContextConfig() { assertThat(this.employee).as("The employee should have been autowired.").isNotNull(); assertThat(this.employee.getName()).isEqualTo("John Smith"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderExplicitConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderExplicitConfigClassesInheritedTests.java similarity index 65% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderExplicitConfigClassesInheritedTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderExplicitConfigClassesInheritedTests.java index 3166a6e0b30a..e525be22865d 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderExplicitConfigClassesInheritedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/DefaultLoaderExplicitConfigClassesInheritedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,15 +14,13 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation; +package org.springframework.test.context.config; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.testfixture.beans.Pet; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.support.DelegatingSmartContextLoader; import static org.assertj.core.api.Assertions.assertThat; @@ -35,16 +33,15 @@ * @author Sam Brannen * @since 3.1 */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = DefaultLoaderDefaultConfigClassesInheritedTests.Config.class) -public class DefaultLoaderExplicitConfigClassesInheritedTests extends DefaultLoaderExplicitConfigClassesBaseTests { +@SpringJUnitConfig(DefaultLoaderDefaultConfigClassesInheritedTests.Config.class) +class DefaultLoaderExplicitConfigClassesInheritedTests extends DefaultLoaderExplicitConfigClassesBaseTests { @Autowired - private Pet pet; + Pet pet; @Test - public void verifyPetSetFromExtendedContextConfig() { + void verifyPetSetFromExtendedContextConfig() { assertThat(this.pet).as("The pet should have been autowired.").isNotNull(); assertThat(this.pet.getName()).isEqualTo("Fido"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/ExplicitConfigClassesBaseTests.java b/spring-test/src/test/java/org/springframework/test/context/config/ExplicitConfigClassesBaseTests.java similarity index 67% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/ExplicitConfigClassesBaseTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/ExplicitConfigClassesBaseTests.java index 8660f67e9547..d35c53b7e591 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/ExplicitConfigClassesBaseTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/ExplicitConfigClassesBaseTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,15 +14,13 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation; +package org.springframework.test.context.config; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.testfixture.beans.Employee; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.support.AnnotationConfigContextLoader; import static org.assertj.core.api.Assertions.assertThat; @@ -36,16 +34,15 @@ * @author Sam Brannen * @since 3.1 */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = DefaultConfigClassesBaseTests.ContextConfiguration.class) -public class ExplicitConfigClassesBaseTests { +@SpringJUnitConfig(loader = AnnotationConfigContextLoader.class, classes = DefaultConfigClassesBaseTests.ContextConfiguration.class) +class ExplicitConfigClassesBaseTests { @Autowired - protected Employee employee; + Employee employee; @Test - public void verifyEmployeeSetFromBaseContextConfig() { + void verifyEmployeeSetFromBaseContextConfig() { assertThat(this.employee).as("The employee should have been autowired.").isNotNull(); assertThat(this.employee.getName()).isEqualTo("John Smith"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/ExplicitConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/config/ExplicitConfigClassesInheritedTests.java similarity index 66% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/ExplicitConfigClassesInheritedTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/ExplicitConfigClassesInheritedTests.java index 3e48c6f6c3d5..1bd8623908f5 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/ExplicitConfigClassesInheritedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/ExplicitConfigClassesInheritedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,15 +14,13 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation; +package org.springframework.test.context.config; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.testfixture.beans.Pet; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.support.AnnotationConfigContextLoader; import static org.assertj.core.api.Assertions.assertThat; @@ -37,16 +35,15 @@ * @author Sam Brannen * @since 3.1 */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = DefaultConfigClassesInheritedTests.ContextConfiguration.class) -public class ExplicitConfigClassesInheritedTests extends ExplicitConfigClassesBaseTests { +@SpringJUnitConfig(loader = AnnotationConfigContextLoader.class, classes = DefaultConfigClassesInheritedTests.ContextConfiguration.class) +class ExplicitConfigClassesInheritedTests extends ExplicitConfigClassesBaseTests { @Autowired - private Pet pet; + Pet pet; @Test - public void verifyPetSetFromExtendedContextConfig() { + void verifyPetSetFromExtendedContextConfig() { assertThat(this.pet).as("The pet should have been autowired.").isNotNull(); assertThat(this.pet.getName()).isEqualTo("Fido"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/InheritedConfigSpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/config/InheritedConfigContextConfigurationAppCtxTests.java similarity index 78% rename from spring-test/src/test/java/org/springframework/test/context/junit4/InheritedConfigSpringJUnit4ClassRunnerAppCtxTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/InheritedConfigContextConfigurationAppCtxTests.java index ccbef06070f0..e2de1bd9f695 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/InheritedConfigSpringJUnit4ClassRunnerAppCtxTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/InheritedConfigContextConfigurationAppCtxTests.java @@ -14,22 +14,22 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.config; import java.lang.annotation.Inherited; import org.springframework.test.context.ContextConfiguration; /** - * Extension of {@link SpringJUnit4ClassRunnerAppCtxTests} which verifies that + * Extension of {@link CoreContextConfigurationAppCtxTests} which verifies that * the configuration of an application context and dependency injection of a * test instance function as expected within a class hierarchy, since * {@link ContextConfiguration configuration} is {@link Inherited inherited}. * * @author Sam Brannen * @since 2.5 - * @see SpringJUnit4ClassRunnerAppCtxTests + * @see CoreContextConfigurationAppCtxTests */ -public class InheritedConfigSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests { +class InheritedConfigContextConfigurationAppCtxTests extends CoreContextConfigurationAppCtxTests { /* all tests are in the parent class. */ } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/config/MultipleResourcesContextConfigurationAppCtxTests.java similarity index 56% rename from spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/MultipleResourcesContextConfigurationAppCtxTests.java index 0206d6f39a07..f2763f8f5d11 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/MultipleResourcesContextConfigurationAppCtxTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 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. @@ -14,33 +14,35 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.config; import org.springframework.test.context.ContextConfiguration; import org.springframework.util.ResourceUtils; /** - * Extension of {@link SpringJUnit4ClassRunnerAppCtxTests}, which verifies that + * Extension of {@link CoreContextConfigurationAppCtxTests}, which verifies that * we can specify multiple resource locations for our application context, each * configured differently. * - *

    {@code MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests} is also used + *

    {@code MultipleResourcesContextConfigurationAppCtxTests} is also used * to verify support for the {@code value} attribute alias for * {@code @ContextConfiguration}'s {@code locations} attribute. * * @author Sam Brannen * @since 2.5 - * @see SpringJUnit4ClassRunnerAppCtxTests + * @see CoreContextConfigurationAppCtxTests */ -@ContextConfiguration( { MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests.CLASSPATH_RESOURCE_PATH, - MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests.LOCAL_RESOURCE_PATH, - MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests.ABSOLUTE_RESOURCE_PATH }) -public class MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests { +@ContextConfiguration( { + MultipleResourcesContextConfigurationAppCtxTests.CLASSPATH_RESOURCE_PATH, + MultipleResourcesContextConfigurationAppCtxTests.LOCAL_RESOURCE_PATH, + MultipleResourcesContextConfigurationAppCtxTests.ABSOLUTE_RESOURCE_PATH +}) +class MultipleResourcesContextConfigurationAppCtxTests extends CoreContextConfigurationAppCtxTests { public static final String CLASSPATH_RESOURCE_PATH = ResourceUtils.CLASSPATH_URL_PREFIX + - "/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context1.xml"; - public static final String LOCAL_RESOURCE_PATH = "MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context2.xml"; - public static final String ABSOLUTE_RESOURCE_PATH = "/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context3.xml"; + "/org/springframework/test/context/config/MultipleResourcesContextConfigurationAppCtxTests-context1.xml"; + public static final String LOCAL_RESOURCE_PATH = "MultipleResourcesContextConfigurationAppCtxTests-context2.xml"; + public static final String ABSOLUTE_RESOURCE_PATH = "/org/springframework/test/context/config/MultipleResourcesContextConfigurationAppCtxTests-context3.xml"; /* all tests are in the parent class. */ diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/OptionalContextConfigurationSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/config/OptionalContextConfigurationTests.java similarity index 65% rename from spring-test/src/test/java/org/springframework/test/context/junit4/OptionalContextConfigurationSpringRunnerTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/OptionalContextConfigurationTests.java index 71bced98d412..7144b8b380ce 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/OptionalContextConfigurationSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/OptionalContextConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,45 +14,42 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.config; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; /** - * JUnit 4 based integration test which verifies that + * JUnit based integration test which verifies that * {@link ContextConfiguration @ContextConfiguration} is optional. * * @author Phillip Webb * @author Sam Brannen * @since 4.3 */ -@RunWith(SpringRunner.class) -public class OptionalContextConfigurationSpringRunnerTests { - - @Autowired - String foo; - +@ExtendWith(SpringExtension.class) +class OptionalContextConfigurationTests { @Test - public void contextConfigurationAnnotationIsOptional() { - assertThat(foo).isEqualTo("foo"); + void contextConfigurationAnnotationIsOptional(@Autowired String foo) { + assertThat(foo).isEqualTo("bar"); } - @Configuration + @Configuration(proxyBeanMethods = false) static class Config { @Bean String foo() { - return "foo"; + return "bar"; } } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/PojoAndStringConfig.java b/spring-test/src/test/java/org/springframework/test/context/config/PojoAndStringConfig.java similarity index 87% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/PojoAndStringConfig.java rename to spring-test/src/test/java/org/springframework/test/context/config/PojoAndStringConfig.java index 8de202408efd..adc255e6891e 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/PojoAndStringConfig.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/PojoAndStringConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2025 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation; +package org.springframework.test.context.config; import org.springframework.beans.testfixture.beans.Employee; import org.springframework.beans.testfixture.beans.Pet; @@ -25,14 +25,14 @@ * ApplicationContext configuration class for various integration tests. * *

    The beans defined in this configuration class map directly to the - * beans defined in {@code SpringJUnit4ClassRunnerAppCtxTests-context.xml}. + * beans defined in {@code CoreContextConfigurationAppCtxTests-context.xml}. * Consequently, the application contexts loaded from these two sources * should be identical with regard to bean definitions. * * @author Sam Brannen * @since 3.1 */ -@Configuration +@Configuration(proxyBeanMethods = false) public class PojoAndStringConfig { @Bean diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/RelativePathSpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/config/RelativePathContextConfigurationAppCtxTests.java similarity index 62% rename from spring-test/src/test/java/org/springframework/test/context/junit4/RelativePathSpringJUnit4ClassRunnerAppCtxTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/RelativePathContextConfigurationAppCtxTests.java index 5a64bdab5607..88c79e77c4b6 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/RelativePathSpringJUnit4ClassRunnerAppCtxTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/RelativePathContextConfigurationAppCtxTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2025 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. @@ -14,21 +14,21 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.config; import org.springframework.test.context.ContextConfiguration; /** - * Extension of {@link SpringJUnit4ClassRunnerAppCtxTests}, which verifies that + * Extension of {@link CoreContextConfigurationAppCtxTests}, which verifies that * we can specify an explicit, relative path location for our * application context. * * @author Sam Brannen * @since 2.5 - * @see SpringJUnit4ClassRunnerAppCtxTests - * @see AbsolutePathSpringJUnit4ClassRunnerAppCtxTests + * @see CoreContextConfigurationAppCtxTests + * @see AbsolutePathContextConfigurationAppCtxTests */ -@ContextConfiguration(locations = { "SpringJUnit4ClassRunnerAppCtxTests-context.xml" }) -public class RelativePathSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests { +@ContextConfiguration(locations = "CoreContextConfigurationAppCtxTests-context.xml", inheritLocations = false) +class RelativePathContextConfigurationAppCtxTests extends CoreContextConfigurationAppCtxTests { /* all tests are in the parent class. */ } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfig.java b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfig.java similarity index 97% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfig.java rename to spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfig.java index 3da514898986..3c144634e5e3 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfig.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfig.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation.meta; +package org.springframework.test.context.config.meta; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigTests.java similarity index 73% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigTests.java index d48627f60f12..8543d1812d6b 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation.meta; +package org.springframework.test.context.config.meta; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; @@ -31,16 +31,17 @@ * @author Sam Brannen * @since 4.0.3 */ -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfig -public class ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigTests { +class ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigTests { @Autowired private String foo; @Test - public void foo() { + void foo() { assertThat(foo).isEqualTo("Resolver Foo"); } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigWithOverridesTests.java b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigWithOverridesTests.java similarity index 83% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigWithOverridesTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigWithOverridesTests.java index 70be20850162..8cdc72c97390 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigWithOverridesTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigWithOverridesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,17 +14,17 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation.meta; +package org.springframework.test.context.config.meta; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.test.context.ActiveProfilesResolver; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; @@ -35,18 +35,19 @@ * @author Sam Brannen * @since 4.0.3 */ -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfig(classes = LocalDevConfig.class, resolver = DevResolver.class) -public class ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigWithOverridesTests { +class ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfigWithOverridesTests { @Autowired private String foo; @Test - public void foo() { + void foo() { assertThat(foo).isEqualTo("Local Dev Foo"); } + } @Configuration diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesMetaConfig.java b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfilesMetaConfig.java similarity index 96% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesMetaConfig.java rename to spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfilesMetaConfig.java index e8fc3927cca2..3270e81b177a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesMetaConfig.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfilesMetaConfig.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation.meta; +package org.springframework.test.context.config.meta; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesMetaConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfilesMetaConfigTests.java similarity index 74% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesMetaConfigTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfilesMetaConfigTests.java index 469870028ec9..99e127dadc6c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesMetaConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfilesMetaConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,16 +14,16 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation.meta; +package org.springframework.test.context.config.meta; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; @@ -35,37 +35,38 @@ * @author Sam Brannen * @since 4.0.3 */ -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @ConfigClassesAndProfilesMetaConfig(profiles = "dev") -public class ConfigClassesAndProfilesMetaConfigTests { +class ConfigClassesAndProfilesMetaConfigTests { - @Configuration + @Autowired + String foo; + + + @Test + void foo() { + assertThat(foo).isEqualTo("Local Dev Foo"); + } + + + @Configuration(proxyBeanMethods = false) @Profile("dev") static class DevConfig { @Bean - public String foo() { + String foo() { return "Local Dev Foo"; } } - @Configuration + @Configuration(proxyBeanMethods = false) @Profile("prod") static class ProductionConfig { @Bean - public String foo() { + String foo() { return "Local Production Foo"; } } - - @Autowired - private String foo; - - - @Test - public void foo() { - assertThat(foo).isEqualTo("Local Dev Foo"); - } } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfig.java b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfig.java similarity index 96% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfig.java rename to spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfig.java index 44684649b2e9..00bd41363c90 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfig.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfig.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation.meta; +package org.springframework.test.context.config.meta; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfigTests.java similarity index 72% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfigTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfigTests.java index f6a8bf102b20..3a9bde4f50b7 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation.meta; +package org.springframework.test.context.config.meta; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; @@ -31,16 +31,17 @@ * @author Sam Brannen * @since 4.0 */ -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @ConfigClassesAndProfilesWithCustomDefaultsMetaConfig -public class ConfigClassesAndProfilesWithCustomDefaultsMetaConfigTests { +class ConfigClassesAndProfilesWithCustomDefaultsMetaConfigTests { @Autowired - private String foo; + String foo; @Test - public void foo() { + void foo() { assertThat(foo).isEqualTo("Dev Foo"); } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfigWithOverridesTests.java b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfigWithOverridesTests.java similarity index 68% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfigWithOverridesTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfigWithOverridesTests.java index 3da7e5ed8584..3ef9fd5a2d64 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfigWithOverridesTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfigWithOverridesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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. @@ -14,17 +14,17 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation.meta; +package org.springframework.test.context.config.meta; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.testfixture.beans.Employee; import org.springframework.beans.testfixture.beans.Pet; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.junit4.annotation.PojoAndStringConfig; -import org.springframework.test.context.junit4.annotation.meta.ConfigClassesAndProfilesWithCustomDefaultsMetaConfig.ProductionConfig; +import org.springframework.test.context.config.PojoAndStringConfig; +import org.springframework.test.context.config.meta.ConfigClassesAndProfilesWithCustomDefaultsMetaConfig.ProductionConfig; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; @@ -35,35 +35,35 @@ * @author Sam Brannen * @since 4.0 */ -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @ConfigClassesAndProfilesWithCustomDefaultsMetaConfig( classes = { PojoAndStringConfig.class, ProductionConfig.class }, profiles = "prod") -public class ConfigClassesAndProfilesWithCustomDefaultsMetaConfigWithOverridesTests { +class ConfigClassesAndProfilesWithCustomDefaultsMetaConfigWithOverridesTests { @Autowired - private String foo; + String foo; @Autowired - private Pet pet; + Pet pet; @Autowired - protected Employee employee; + Employee employee; @Test - public void verifyEmployee() { + void verifyEmployee() { assertThat(this.employee).as("The employee should have been autowired.").isNotNull(); assertThat(this.employee.getName()).isEqualTo("John Smith"); } @Test - public void verifyPet() { + void verifyPet() { assertThat(this.pet).as("The pet should have been autowired.").isNotNull(); assertThat(this.pet.getName()).isEqualTo("Fido"); } @Test - public void verifyFoo() { + void verifyFoo() { assertThat(this.foo).isEqualTo("Production Foo"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaMetaConfig.java b/spring-test/src/test/java/org/springframework/test/context/config/meta/MetaMetaConfig.java similarity index 95% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaMetaConfig.java rename to spring-test/src/test/java/org/springframework/test/context/config/meta/MetaMetaConfig.java index be59c1119f02..949cd0c476e3 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaMetaConfig.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/meta/MetaMetaConfig.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation.meta; +package org.springframework.test.context.config.meta; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaMetaConfigDefaultsTests.java b/spring-test/src/test/java/org/springframework/test/context/config/meta/MetaMetaConfigDefaultsTests.java similarity index 73% rename from spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaMetaConfigDefaultsTests.java rename to spring-test/src/test/java/org/springframework/test/context/config/meta/MetaMetaConfigDefaultsTests.java index 65fe0c5da304..be408edf95b8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaMetaConfigDefaultsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/config/meta/MetaMetaConfigDefaultsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.annotation.meta; +package org.springframework.test.context.config.meta; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; @@ -32,16 +32,17 @@ * @author Sam Brannen * @since 4.0.3 */ -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @MetaMetaConfig -public class MetaMetaConfigDefaultsTests { +class MetaMetaConfigDefaultsTests { @Autowired - private String foo; + String foo; @Test - public void foo() { + void foo() { assertThat(foo).isEqualTo("Production Foo"); } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextInterfaceTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextInterfaceTests.java index 92d1331bd69a..4a2cbfa492e8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextInterfaceTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextInterfaceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 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. @@ -21,21 +21,22 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.testkit.engine.EngineTestKit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.TestExecutionListeners; -import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.springframework.test.context.cache.ContextCacheTestUtils.assertContextCacheStatistics; import static org.springframework.test.context.cache.ContextCacheTestUtils.resetContextCache; -import static org.springframework.test.context.junit4.JUnitTestingUtils.runTestsAndAssertCounters; /** * @author Sam Brannen @@ -70,11 +71,14 @@ void verifyDirtiesContextBehavior() throws Exception { } private void runTestClassAndAssertStats(Class testClass, int expectedTestCount) throws Exception { - runTestsAndAssertCounters(testClass, expectedTestCount, 0, expectedTestCount, 0, 0); + EngineTestKit.engine("junit-jupiter") + .selectors(selectClass(testClass)) + .execute() + .testEvents() + .assertStatistics(stats -> stats.started(expectedTestCount).succeeded(expectedTestCount).failed(0)); } - - @RunWith(SpringRunner.class) + @ExtendWith(SpringExtension.class) // Ensure that we do not include the EventPublishingTestExecutionListener // since it will access the ApplicationContext for each method in the // TestExecutionListener API, thus distorting our cache hit/miss results. @@ -90,13 +94,13 @@ public static class ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCa ApplicationContext applicationContext; - @org.junit.Test + @Test public void verifyContextWasAutowired() { assertThat(this.applicationContext).as("The application context should have been autowired.").isNotNull(); } - @Configuration + @Configuration(proxyBeanMethods = false) static class Config { /* no beans */ } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/hybrid/HybridContextLoader.java b/spring-test/src/test/java/org/springframework/test/context/hybrid/HybridContextLoader.java similarity index 98% rename from spring-test/src/test/java/org/springframework/test/context/junit4/hybrid/HybridContextLoader.java rename to spring-test/src/test/java/org/springframework/test/context/hybrid/HybridContextLoader.java index 9f8c06534ca1..9b2375fcdfc0 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/hybrid/HybridContextLoader.java +++ b/spring-test/src/test/java/org/springframework/test/context/hybrid/HybridContextLoader.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.hybrid; +package org.springframework.test.context.hybrid; import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/hybrid/HybridContextLoaderTests.java b/spring-test/src/test/java/org/springframework/test/context/hybrid/HybridContextLoaderTests.java similarity index 76% rename from spring-test/src/test/java/org/springframework/test/context/junit4/hybrid/HybridContextLoaderTests.java rename to spring-test/src/test/java/org/springframework/test/context/hybrid/HybridContextLoaderTests.java index 1ef7a18c09fe..07b630c9106e 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/hybrid/HybridContextLoaderTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/hybrid/HybridContextLoaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -14,17 +14,17 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.hybrid; +package org.springframework.test.context.hybrid; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.SmartContextLoader; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; @@ -37,37 +37,22 @@ * @since 4.0.4 * @see HybridContextLoader */ -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @ContextConfiguration(loader = HybridContextLoader.class) -public class HybridContextLoaderTests { - - @Configuration - static class Config { - - @Bean - public String fooFromJava() { - return "Java"; - } - - @Bean - public String enigma() { - return "enigma from Java"; - } - } - +class HybridContextLoaderTests { @Autowired - private String fooFromXml; + String fooFromXml; @Autowired - private String fooFromJava; + String fooFromJava; @Autowired - private String enigma; + String enigma; @Test - public void verifyContentsOfHybridApplicationContext() { + void verifyContentsOfHybridApplicationContext() { assertThat(fooFromXml).isEqualTo("XML"); assertThat(fooFromJava).isEqualTo("Java"); @@ -78,4 +63,19 @@ public void verifyContentsOfHybridApplicationContext() { assertThat(enigma).isEqualTo("enigma from XML"); } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + String fooFromJava() { + return "Java"; + } + + @Bean + String enigma() { + return "enigma from Java"; + } + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/BeanOverridingDefaultLocationsInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/inheritance/BeanOverridingDefaultLocationsInheritedTests.java similarity index 78% rename from spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/BeanOverridingDefaultLocationsInheritedTests.java rename to spring-test/src/test/java/org/springframework/test/context/inheritance/BeanOverridingDefaultLocationsInheritedTests.java index f33a0c4fcbcb..0b72dea1992f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/BeanOverridingDefaultLocationsInheritedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/inheritance/BeanOverridingDefaultLocationsInheritedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,16 +14,16 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.spr3896; +package org.springframework.test.context.inheritance; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.test.context.ContextConfiguration; import static org.assertj.core.api.Assertions.assertThat; /** - * JUnit 4 based integration test for verifying support for the + * JUnit based integration test for verifying support for the * {@link ContextConfiguration#inheritLocations() inheritLocations} flag of * {@link ContextConfiguration @ContextConfiguration} indirectly proposed in This suite is only intended to be used manually within an IDE. + * + *

    Logging Configuration

    + * + *

    In order for our log4j2 configuration to be used in an IDE, you must + * set the following system property before running any tests — for + * example, in Run Configurations in Eclipse. + * + *

    + * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
    + * 
    + * + * @author Sam Brannen + * @since 3.2 + */ +@Suite +@IncludeEngines("junit-jupiter") +@SelectPackages({ + "org.springframework.test.context.initializers.annotation", + "org.springframework.test.context.initializers.xml" +}) +@IncludeClassNamePatterns(".*Tests$") +@ExcludeTags("failing-test-case") +@ConfigurationParameter( + key = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME, + value = "org.junit.jupiter.api.ClassOrderer$ClassName" +) +public class AciTestSuite { +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/DevProfileInitializer.java b/spring-test/src/test/java/org/springframework/test/context/initializers/DevProfileInitializer.java similarity index 90% rename from spring-test/src/test/java/org/springframework/test/context/junit4/aci/DevProfileInitializer.java rename to spring-test/src/test/java/org/springframework/test/context/initializers/DevProfileInitializer.java index 16a4ce4ec5ae..04f866aee327 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/DevProfileInitializer.java +++ b/spring-test/src/test/java/org/springframework/test/context/initializers/DevProfileInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2025 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.aci; +package org.springframework.test.context.initializers; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.support.GenericApplicationContext; @@ -29,4 +29,5 @@ public class DevProfileInitializer implements ApplicationContextInitializergh-13491
    . + * + *

    Work Around

    + *

    By using a SpEL expression to generate a random {@code database-name} + * for the embedded database (see {@code datasource-config.xml}), we ensure + * that each {@code ApplicationContext} that imports the common configuration + * will create an embedded database with a unique name. + * + *

    To reproduce the problem mentioned in gh-13491, delete the declaration + * of the {@code database-name} attribute of the embedded database in + * {@code datasource-config.xml} and run this suite. + * + *

    Solution

    + *

    As of Spring 4.2, a proper solution is possible thanks to gh-13491. + * {@link TestClass2A} and {@link TestClass2B} both import + * {@code datasource-config-with-auto-generated-db-name.xml} which makes + * use of the new {@code generate-name} attribute of {@code }. + * + * @author Sam Brannen + * @author Mickael Leduque + */ +class GeneratedDatabaseNamesTests { + + private static final String DATASOURCE_CONFIG_XML = + "classpath:/org/springframework/test/context/jdbc/datasource-config.xml"; + + private static final String DATASOURCE_CONFIG_WITH_AUTO_GENERATED_DB_NAME_XML = + "classpath:/org/springframework/test/context/jdbc/datasource-config-with-auto-generated-db-name.xml"; + + + @Test + void runTestsWithGeneratedDatabaseNames() { + EngineTestKit.engine("junit-jupiter") + .selectors( + selectClass(TestClass1A.class), + selectClass(TestClass1B.class), + selectClass(TestClass2A.class), + selectClass(TestClass2B.class) + ) + .execute() + .testEvents() + .assertStatistics(stats -> stats.started(4).succeeded(4).failed(0)); + } + + + @ExtendWith(SpringExtension.class) + abstract static class AbstractTestCase { + + @Resource + DataSource dataSource; + + @Test + void test() { + assertThat(dataSource).isNotNull(); + } + } + + @ContextConfiguration + static class TestClass1A extends AbstractTestCase { + + @Configuration + @ImportResource(DATASOURCE_CONFIG_XML) + static class Config { + } + } + + @ContextConfiguration + static class TestClass1B extends AbstractTestCase { + + @Configuration + @ImportResource(DATASOURCE_CONFIG_XML) + static class Config { + } + } + + /** + * @since 4.2 + */ + @ContextConfiguration + static class TestClass2A extends AbstractTestCase { + + @Configuration + @ImportResource(DATASOURCE_CONFIG_WITH_AUTO_GENERATED_DB_NAME_XML) + static class Config { + } + } + + /** + * @since 4.2 + */ + @ContextConfiguration + static class TestClass2B extends AbstractTestCase { + + @Configuration + @ImportResource(DATASOURCE_CONFIG_WITH_AUTO_GENERATED_DB_NAME_XML) + static class Config { + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr4868/Jsr250LifecycleTests.java b/spring-test/src/test/java/org/springframework/test/context/jsr250/Jsr250LifecycleTests.java similarity index 58% rename from spring-test/src/test/java/org/springframework/test/context/junit4/spr4868/Jsr250LifecycleTests.java rename to spring-test/src/test/java/org/springframework/test/context/jsr250/Jsr250LifecycleTests.java index cc2c82c966f2..84c996a71e40 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr4868/Jsr250LifecycleTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jsr250/Jsr250LifecycleTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -14,23 +14,21 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.spr4868; +package org.springframework.test.context.jsr250; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import static org.assertj.core.api.Assertions.assertThat; @@ -46,73 +44,71 @@ * *

    Expected Log Output

    *
    - * INFO : org.springframework.test.context.junit4.spr4868.LifecycleBean - initializing
    - * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - beforeAllTests()
    - * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - setUp()
    - * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - test1()
    - * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - tearDown()
    - * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - beforeAllTests()
    - * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - setUp()
    - * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - test2()
    - * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - tearDown()
    - * INFO : org.springframework.test.context.junit4.spr4868.LifecycleBean - destroying
    + * INFO : org.springframework.test.context.jsr250.LifecycleBean - initializing
    + * INFO : org.springframework.test.context.jsr250.Jsr250LifecycleTests - beforeAllTests()
    + * INFO : org.springframework.test.context.jsr250.Jsr250LifecycleTests - setUp()
    + * INFO : org.springframework.test.context.jsr250.Jsr250LifecycleTests - test1()
    + * INFO : org.springframework.test.context.jsr250.Jsr250LifecycleTests - tearDown()
    + * INFO : org.springframework.test.context.jsr250.Jsr250LifecycleTests - beforeAllTests()
    + * INFO : org.springframework.test.context.jsr250.Jsr250LifecycleTests - setUp()
    + * INFO : org.springframework.test.context.jsr250.Jsr250LifecycleTests - test2()
    + * INFO : org.springframework.test.context.jsr250.Jsr250LifecycleTests - tearDown()
    + * INFO : org.springframework.test.context.jsr250.LifecycleBean - destroying
      * 
    * * @author Sam Brannen * @since 3.2 */ -@RunWith(SpringJUnit4ClassRunner.class) +@SpringJUnitConfig @TestExecutionListeners(DependencyInjectionTestExecutionListener.class) -@ContextConfiguration -public class Jsr250LifecycleTests { +class Jsr250LifecycleTests { private final Log logger = LogFactory.getLog(Jsr250LifecycleTests.class); - - @Configuration - static class Config { - - @Bean - public LifecycleBean lifecycleBean() { - return new LifecycleBean(); - } - } - - @Autowired - private LifecycleBean lifecycleBean; + LifecycleBean lifecycleBean; @PostConstruct - public void beforeAllTests() { + void beforeAllTests() { logger.info("beforeAllTests()"); } @PreDestroy - public void afterTestSuite() { + void afterTestSuite() { logger.info("afterTestSuite()"); } - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() { logger.info("setUp()"); } - @After - public void tearDown() { + @AfterEach + void tearDown() { logger.info("tearDown()"); } @Test - public void test1() { + void test1() { logger.info("test1()"); assertThat(lifecycleBean).isNotNull(); } @Test - public void test2() { + void test2() { logger.info("test2()"); assertThat(lifecycleBean).isNotNull(); } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + LifecycleBean lifecycleBean() { + return new LifecycleBean(); + } + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr4868/LifecycleBean.java b/spring-test/src/test/java/org/springframework/test/context/jsr250/LifecycleBean.java similarity index 94% rename from spring-test/src/test/java/org/springframework/test/context/junit4/spr4868/LifecycleBean.java rename to spring-test/src/test/java/org/springframework/test/context/jsr250/LifecycleBean.java index fe452ee03fc4..e40a7a8c7258 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr4868/LifecycleBean.java +++ b/spring-test/src/test/java/org/springframework/test/context/jsr250/LifecycleBean.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.spr4868; +package org.springframework.test.context.jsr250; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/JUnitTestingUtils.java b/spring-test/src/test/java/org/springframework/test/context/junit4/JUnitTestingUtils.java index eaefadc1a801..1fcdf1fc2e30 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/JUnitTestingUtils.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/JUnitTestingUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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,26 +38,6 @@ */ public class JUnitTestingUtils { - /** - * Run the tests in the supplied {@code testClass}, using the {@link Runner} - * configured via {@link RunWith @RunWith} or the default JUnit runner, and - * assert the expectations of the test execution. - * - * @param testClass the test class to run with JUnit - * @param expectedStartedCount the expected number of tests that started - * @param expectedFailedCount the expected number of tests that failed - * @param expectedFinishedCount the expected number of tests that finished - * @param expectedIgnoredCount the expected number of tests that were ignored - * @param expectedAssumptionFailedCount the expected number of tests that - * resulted in a failed assumption - */ - public static void runTestsAndAssertCounters(Class testClass, int expectedStartedCount, int expectedFailedCount, - int expectedFinishedCount, int expectedIgnoredCount, int expectedAssumptionFailedCount) throws Exception { - - runTestsAndAssertCounters(null, testClass, expectedStartedCount, expectedFailedCount, expectedFinishedCount, - expectedIgnoredCount, expectedAssumptionFailedCount); - } - /** * Run the tests in the supplied {@code testClass}, using the specified * {@link Runner}, and assert the expectations of the test execution. diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackFalseRollbackAnnotationTransactionalTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackFalseRollbackAnnotationTransactionalTests.java deleted file mode 100644 index acfe92bfcebe..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackFalseRollbackAnnotationTransactionalTests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2002-2019 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.junit4; - -import javax.sql.DataSource; - -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.test.annotation.Rollback; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.transaction.TransactionAssert.assertThatTransaction; - -/** - * Extension of {@link DefaultRollbackFalseRollbackAnnotationTransactionalTests} - * which tests method-level rollback override behavior via the - * {@link Rollback @Rollback} annotation. - * - * @author Sam Brannen - * @since 4.2 - * @see Rollback - */ -public class RollbackOverrideDefaultRollbackFalseRollbackAnnotationTransactionalTests extends - DefaultRollbackFalseRollbackAnnotationTransactionalTests { - - private static int originalNumRows; - - private static JdbcTemplate jdbcTemplate; - - - @Override - @Autowired - public void setDataSource(DataSource dataSource) { - jdbcTemplate = new JdbcTemplate(dataSource); - } - - - @Before - @Override - public void verifyInitialTestData() { - originalNumRows = clearPersonTable(jdbcTemplate); - assertThat(addPerson(jdbcTemplate, BOB)).as("Adding bob").isEqualTo(1); - assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the initial number of rows in the person table.").isEqualTo(1); - } - - @Test - @Rollback - @Override - public void modifyTestDataWithinTransaction() { - assertThatTransaction().isActive(); - assertThat(deletePerson(jdbcTemplate, BOB)).as("Deleting bob").isEqualTo(1); - assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1); - assertThat(addPerson(jdbcTemplate, SUE)).as("Adding sue").isEqualTo(1); - assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table within a transaction.").isEqualTo(2); - } - - @AfterClass - public static void verifyFinalTestData() { - assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(originalNumRows); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackTrueRollbackAnnotationTransactionalTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackTrueRollbackAnnotationTransactionalTests.java deleted file mode 100644 index b9b11774f9fa..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackTrueRollbackAnnotationTransactionalTests.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2002-2019 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.junit4; - -import javax.sql.DataSource; - -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.test.annotation.Rollback; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.transaction.TransactionAssert.assertThatTransaction; - -/** - * Extension of {@link DefaultRollbackTrueRollbackAnnotationTransactionalTests} - * which tests method-level rollback override behavior via the - * {@link Rollback @Rollback} annotation. - * - * @author Sam Brannen - * @since 4.2 - * @see Rollback - */ -public class RollbackOverrideDefaultRollbackTrueRollbackAnnotationTransactionalTests extends - DefaultRollbackTrueRollbackAnnotationTransactionalTests { - - private static JdbcTemplate jdbcTemplate; - - - @Autowired - @Override - public void setDataSource(DataSource dataSource) { - jdbcTemplate = new JdbcTemplate(dataSource); - } - - - @Before - @Override - public void verifyInitialTestData() { - clearPersonTable(jdbcTemplate); - assertThat(addPerson(jdbcTemplate, BOB)).as("Adding bob").isEqualTo(1); - assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the initial number of rows in the person table.").isEqualTo(1); - } - - @Test - @Rollback(false) - @Override - public void modifyTestDataWithinTransaction() { - assertThatTransaction().isActive(); - assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1); - assertThat(addPerson(jdbcTemplate, SUE)).as("Adding sue").isEqualTo(1); - assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table within a transaction.").isEqualTo(3); - } - - @AfterClass - public static void verifyFinalTestData() { - assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(3); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java index 5302d1c0fb59..9ef5d52c9369 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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. @@ -20,27 +20,6 @@ import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; -import org.springframework.test.context.junit4.annotation.AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests; -import org.springframework.test.context.junit4.annotation.BeanOverridingDefaultConfigClassesInheritedTests; -import org.springframework.test.context.junit4.annotation.BeanOverridingExplicitConfigClassesInheritedTests; -import org.springframework.test.context.junit4.annotation.DefaultConfigClassesBaseTests; -import org.springframework.test.context.junit4.annotation.DefaultConfigClassesInheritedTests; -import org.springframework.test.context.junit4.annotation.DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests; -import org.springframework.test.context.junit4.annotation.DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests; -import org.springframework.test.context.junit4.annotation.DefaultLoaderDefaultConfigClassesBaseTests; -import org.springframework.test.context.junit4.annotation.DefaultLoaderDefaultConfigClassesInheritedTests; -import org.springframework.test.context.junit4.annotation.DefaultLoaderExplicitConfigClassesBaseTests; -import org.springframework.test.context.junit4.annotation.DefaultLoaderExplicitConfigClassesInheritedTests; -import org.springframework.test.context.junit4.annotation.ExplicitConfigClassesBaseTests; -import org.springframework.test.context.junit4.annotation.ExplicitConfigClassesInheritedTests; -import org.springframework.test.context.junit4.orm.HibernateSessionFlushingTests; -import org.springframework.test.context.junit4.profile.annotation.DefaultProfileAnnotationConfigTests; -import org.springframework.test.context.junit4.profile.annotation.DevProfileAnnotationConfigTests; -import org.springframework.test.context.junit4.profile.annotation.DevProfileResolverAnnotationConfigTests; -import org.springframework.test.context.junit4.profile.xml.DefaultProfileXmlConfigTests; -import org.springframework.test.context.junit4.profile.xml.DevProfileResolverXmlConfigTests; -import org.springframework.test.context.junit4.profile.xml.DevProfileXmlConfigTests; - /** * JUnit test suite for tests involving {@link SpringRunner} and the * Spring TestContext Framework; only intended to be run manually as a @@ -59,51 +38,18 @@ */ @RunWith(Suite.class) // Note: the following 'multi-line' layout is for enhanced code readability. -@SuiteClasses({// -StandardJUnit4FeaturesTests.class,// - StandardJUnit4FeaturesSpringRunnerTests.class,// - SpringJUnit47ClassRunnerRuleTests.class,// - AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests.class,// - DefaultConfigClassesBaseTests.class,// - DefaultConfigClassesInheritedTests.class,// - BeanOverridingDefaultConfigClassesInheritedTests.class,// - ExplicitConfigClassesBaseTests.class,// - ExplicitConfigClassesInheritedTests.class,// - BeanOverridingExplicitConfigClassesInheritedTests.class,// - DefaultLoaderDefaultConfigClassesBaseTests.class,// - DefaultLoaderDefaultConfigClassesInheritedTests.class,// - DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.class,// - DefaultLoaderExplicitConfigClassesBaseTests.class,// - DefaultLoaderExplicitConfigClassesInheritedTests.class,// - DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.class,// - DefaultProfileAnnotationConfigTests.class,// - DevProfileAnnotationConfigTests.class,// - DevProfileResolverAnnotationConfigTests.class,// - DefaultProfileXmlConfigTests.class,// - DevProfileXmlConfigTests.class,// - DevProfileResolverXmlConfigTests.class,// - ExpectedExceptionSpringRunnerTests.class,// - TimedSpringRunnerTests.class,// - RepeatedSpringRunnerTests.class,// - EnabledAndIgnoredSpringRunnerTests.class,// - HardCodedProfileValueSourceSpringRunnerTests.class,// - SpringJUnit4ClassRunnerAppCtxTests.class,// - ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests.class,// - AbsolutePathSpringJUnit4ClassRunnerAppCtxTests.class,// - RelativePathSpringJUnit4ClassRunnerAppCtxTests.class,// - MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests.class,// - InheritedConfigSpringJUnit4ClassRunnerAppCtxTests.class,// - ParameterizedDependencyInjectionTests.class,// - ConcreteTransactionalJUnit4SpringContextTests.class,// - ClassLevelTransactionalSpringRunnerTests.class,// - MethodLevelTransactionalSpringRunnerTests.class,// - DefaultRollbackTrueRollbackAnnotationTransactionalTests.class,// - DefaultRollbackFalseRollbackAnnotationTransactionalTests.class,// - RollbackOverrideDefaultRollbackTrueTransactionalTests.class,// - RollbackOverrideDefaultRollbackFalseTransactionalTests.class,// - BeforeAndAfterTransactionAnnotationTests.class,// - TimedTransactionalSpringRunnerTests.class,// - HibernateSessionFlushingTests.class // +@SuiteClasses({ + StandardJUnit4FeaturesTests.class, + StandardJUnit4FeaturesSpringRunnerTests.class, + SpringJUnit47ClassRunnerRuleTests.class, + ExpectedExceptionSpringRunnerTests.class, + TimedSpringRunnerTests.class, + RepeatedSpringRunnerTests.class, + EnabledAndIgnoredSpringRunnerTests.class, + HardCodedProfileValueSourceSpringRunnerTests.class, + ParameterizedDependencyInjectionTests.class, + ConcreteTransactionalJUnit4SpringContextTests.class, + TimedTransactionalSpringRunnerTests.class }) public class SpringJUnit4TestSuite { /* this test case consists entirely of tests loaded as a suite. */ diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/TimedTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedTransactionalSpringRunnerTests.java index ae648d0d20e7..1262248c6772 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/TimedTransactionalSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedTransactionalSpringRunnerTests.java @@ -38,7 +38,7 @@ * @see org.springframework.test.context.junit.jupiter.transaction.TimedTransactionalSpringExtensionTests */ @RunWith(SpringRunner.class) -@ContextConfiguration("transactionalTests-context.xml") +@ContextConfiguration("/org/springframework/test/context/transaction/transactionalTests-context.xml") @Transactional public class TimedTransactionalSpringRunnerTests { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/AciTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/AciTestSuite.java deleted file mode 100644 index 30d70eefd4f1..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/AciTestSuite.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2002-2012 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.junit4.aci; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.test.context.junit4.aci.annotation.InitializerWithoutConfigFilesOrClassesTests; -import org.springframework.test.context.junit4.aci.annotation.MergedInitializersAnnotationConfigTests; -import org.springframework.test.context.junit4.aci.annotation.MultipleInitializersAnnotationConfigTests; -import org.springframework.test.context.junit4.aci.annotation.OrderedInitializersAnnotationConfigTests; -import org.springframework.test.context.junit4.aci.annotation.OverriddenInitializersAnnotationConfigTests; -import org.springframework.test.context.junit4.aci.annotation.SingleInitializerAnnotationConfigTests; -import org.springframework.test.context.junit4.aci.xml.MultipleInitializersXmlConfigTests; - -/** - * Convenience test suite for integration tests that verify support for - * {@link ApplicationContextInitializer ApplicationContextInitializers} (ACIs) - * in the TestContext framework. - * - * @author Sam Brannen - * @since 3.2 - */ -@RunWith(Suite.class) -// Note: the following 'multi-line' layout is for enhanced code readability. -@SuiteClasses({// - MultipleInitializersXmlConfigTests.class,// - SingleInitializerAnnotationConfigTests.class,// - MultipleInitializersAnnotationConfigTests.class,// - MergedInitializersAnnotationConfigTests.class,// - OverriddenInitializersAnnotationConfigTests.class,// - OrderedInitializersAnnotationConfigTests.class,// - InitializerWithoutConfigFilesOrClassesTests.class // -}) -public class AciTestSuite { -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/AnnotationConfigTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/AnnotationConfigTestSuite.java deleted file mode 100644 index f5f16ea99371..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/AnnotationConfigTestSuite.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2002-2011 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.junit4.annotation; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -/** - * JUnit test suite for annotation-driven configuration class - * support in the Spring TestContext Framework. - * - * @author Sam Brannen - * @since 3.1 - */ -@RunWith(Suite.class) -// Note: the following 'multi-line' layout is for enhanced code readability. -@SuiteClasses({// -AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests.class,// - DefaultConfigClassesBaseTests.class,// - DefaultConfigClassesInheritedTests.class,// - BeanOverridingDefaultConfigClassesInheritedTests.class,// - ExplicitConfigClassesBaseTests.class,// - ExplicitConfigClassesInheritedTests.class,// - BeanOverridingExplicitConfigClassesInheritedTests.class,// - DefaultLoaderDefaultConfigClassesBaseTests.class,// - DefaultLoaderDefaultConfigClassesInheritedTests.class,// - DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.class,// - DefaultLoaderExplicitConfigClassesBaseTests.class,// - DefaultLoaderExplicitConfigClassesInheritedTests.class,// - DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.class // -}) -public class AnnotationConfigTestSuite { -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/concurrency/SpringJUnit4ConcurrencyTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/concurrency/SpringJUnit4ConcurrencyTests.java index ee61b3ebeff1..a5400ab4dccf 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/concurrency/SpringJUnit4ConcurrencyTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/concurrency/SpringJUnit4ConcurrencyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -25,10 +25,7 @@ import org.junit.experimental.ParallelComputer; import org.springframework.core.testfixture.TestGroup; -import org.springframework.test.context.junit4.InheritedConfigSpringJUnit4ClassRunnerAppCtxTests; -import org.springframework.test.context.junit4.MethodLevelTransactionalSpringRunnerTests; import org.springframework.test.context.junit4.SpringJUnit47ClassRunnerRuleTests; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunnerAppCtxTests; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.TimedTransactionalSpringRunnerTests; import org.springframework.test.context.junit4.rules.BaseAppCtxRuleTests; @@ -70,16 +67,12 @@ public class SpringJUnit4ConcurrencyTests { private final Class[] testClasses = new Class[] { // Basics - SpringJUnit4ClassRunnerAppCtxTests.class, - InheritedConfigSpringJUnit4ClassRunnerAppCtxTests.class, SpringJUnit47ClassRunnerRuleTests.class, BaseAppCtxRuleTests.class, // Transactional - MethodLevelTransactionalSpringRunnerTests.class, TimedTransactionalSpringRunnerTests.class, // Web and Scopes BasicAnnotationConfigWacSpringRuleTests.class, - // Spring MVC Test }; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java deleted file mode 100644 index 26d29dfa264f..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2002-2013 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.junit4.profile.annotation; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -/** - * JUnit test suite for bean definition profile support in the - * Spring TestContext Framework with annotation-based configuration. - * - * @author Sam Brannen - * @since 3.1 - */ -@RunWith(Suite.class) -// Note: the following 'multi-line' layout is for enhanced code readability. -@SuiteClasses({// -DefaultProfileAnnotationConfigTests.class,// - DevProfileAnnotationConfigTests.class,// - DevProfileResolverAnnotationConfigTests.class // -}) -public class ProfileAnnotationConfigTestSuite { -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolverTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolverTests.java deleted file mode 100644 index 6c71af3fbdf0..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolverTests.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2002-2019 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.junit4.profile.resolver; - -import java.util.Arrays; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Michail Nikolaev - * @since 4.0 - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration -@ActiveProfiles(resolver = ClassNameActiveProfilesResolver.class) -public class ClassNameActiveProfilesResolverTests { - - @Configuration - static class Config { - - } - - - @Autowired - private ApplicationContext applicationContext; - - - @Test - public void test() { - assertThat(Arrays.asList(applicationContext.getEnvironment().getActiveProfiles()).contains( - getClass().getSimpleName().toLowerCase())).isTrue(); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/ProfileXmlConfigTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/ProfileXmlConfigTestSuite.java deleted file mode 100644 index 741327d7e198..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/ProfileXmlConfigTestSuite.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2002-2013 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.junit4.profile.xml; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -/** - * JUnit test suite for bean definition profile support in the - * Spring TestContext Framework with XML-based configuration. - * - * @author Sam Brannen - * @since 3.1 - */ -@RunWith(Suite.class) -// Note: the following 'multi-line' layout is for enhanced code readability. -@SuiteClasses({// -DefaultProfileXmlConfigTests.class,// - DevProfileXmlConfigTests.class,// - DevProfileResolverXmlConfigTests.class // -}) -public class ProfileXmlConfigTestSuite { -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/rules/BasicAnnotationConfigWacSpringRuleTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/rules/BasicAnnotationConfigWacSpringRuleTests.java index 8c2f26f8fda4..326e5f5621f3 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/rules/BasicAnnotationConfigWacSpringRuleTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/rules/BasicAnnotationConfigWacSpringRuleTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2025 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. @@ -18,23 +18,30 @@ import org.junit.ClassRule; import org.junit.Rule; +import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.springframework.test.context.web.BasicAnnotationConfigWacTests; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.web.AbstractBasicWacTests; +import org.springframework.test.context.web.ServletContextAwareBean; + +import static org.assertj.core.api.Assertions.assertThat; /** - * This class is an extension of {@link BasicAnnotationConfigWacTests} - * that has been modified to use {@link SpringClassRule} and - * {@link SpringMethodRule}. + * This class is a copy of {@link org.springframework.test.context.web.BasicAnnotationConfigWacTests} + * that has been modified to use the {@link JUnit4} runner combined with + * {@link SpringClassRule} and {@link SpringMethodRule}. * * @author Sam Brannen * @since 4.2 */ @RunWith(JUnit4.class) -public class BasicAnnotationConfigWacSpringRuleTests extends BasicAnnotationConfigWacTests { - - // All tests are in superclass. +@ContextConfiguration +public class BasicAnnotationConfigWacSpringRuleTests extends AbstractBasicWacTests { @ClassRule public static final SpringClassRule springClassRule = new SpringClassRule(); @@ -42,4 +49,46 @@ public class BasicAnnotationConfigWacSpringRuleTests extends BasicAnnotationConf @Rule public final SpringMethodRule springMethodRule = new SpringMethodRule(); + + @Autowired + ServletContextAwareBean servletContextAwareBean; + + + /** + * Have to override this method to annotate it with JUnit 4's {@code @Test} + * annotation. + */ + @Test + @Override + public void basicWacFeatures() throws Exception { + super.basicWacFeatures(); + } + + @Test + public void fooEnigmaAutowired() { + assertThat(foo).isEqualTo("enigma"); + } + + @Test + public void servletContextAwareBeanProcessed() { + assertThat(servletContextAwareBean).isNotNull(); + assertThat(servletContextAwareBean.getServletContext()).isNotNull(); + } + + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + String foo() { + return "enigma"; + } + + @Bean + ServletContextAwareBean servletContextAwareBean() { + return new ServletContextAwareBean(); + } + + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/rules/BeforeAndAfterTransactionAnnotationSpringRuleTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/rules/BeforeAndAfterTransactionAnnotationSpringRuleTests.java index bff96a6fe8db..24f6acba7d86 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/rules/BeforeAndAfterTransactionAnnotationSpringRuleTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/rules/BeforeAndAfterTransactionAnnotationSpringRuleTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2025 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,23 +16,52 @@ package org.springframework.test.context.junit4.rules; +import javax.sql.DataSource; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.springframework.test.context.junit4.BeforeAndAfterTransactionAnnotationTests; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.transaction.AfterTransaction; +import org.springframework.test.context.transaction.BeforeTransaction; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.transaction.TransactionAssert.assertThatTransaction; /** - * This class is an extension of {@link BeforeAndAfterTransactionAnnotationTests} - * that has been modified to use {@link SpringClassRule} and + * This class is a copy of {@code BeforeAndAfterTransactionAnnotationTests} + * that has been modified to use JUnit 4, {@link SpringClassRule}, and * {@link SpringMethodRule}. * * @author Sam Brannen * @since 4.2 */ @RunWith(JUnit4.class) -public class BeforeAndAfterTransactionAnnotationSpringRuleTests extends BeforeAndAfterTransactionAnnotationTests { +@ContextConfiguration("/org/springframework/test/context/transaction/transactionalTests-context.xml") +@Transactional +public class BeforeAndAfterTransactionAnnotationSpringRuleTests { + + private static final String JANE = "jane"; + private static final String SUE = "sue"; + private static final String LUKE = "luke"; + private static final String LEIA = "leia"; + private static final String YODA = "yoda"; + + private static int numBeforeTransactionCalls = 0; + private static int numAfterTransactionCalls = 0; + @ClassRule public static final SpringClassRule springClassRule = new SpringClassRule(); @@ -40,6 +69,116 @@ public class BeforeAndAfterTransactionAnnotationSpringRuleTests extends BeforeAn @Rule public final SpringMethodRule springMethodRule = new SpringMethodRule(); - // All tests are in superclass. + @Rule + public final TestName testName = new TestName(); + + + static JdbcTemplate jdbcTemplate; + + boolean inTransaction = false; + + + @Autowired + void setDataSource(DataSource dataSource) { + jdbcTemplate = new JdbcTemplate(dataSource); + } + + + @BeforeClass + public static void beforeClass() { + numBeforeTransactionCalls = 0; + numAfterTransactionCalls = 0; + } + + @AfterClass + public static void afterClass() { + assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(3); + assertThat(numBeforeTransactionCalls).as("Verifying the total number of calls to beforeTransaction().").isEqualTo(2); + assertThat(numAfterTransactionCalls).as("Verifying the total number of calls to afterTransaction().").isEqualTo(2); + } + + @BeforeTransaction + void beforeTransaction() { + assertThatTransaction().isNotActive(); + this.inTransaction = true; + numBeforeTransactionCalls++; + clearPersonTable(jdbcTemplate); + assertThat(addPerson(jdbcTemplate, YODA)).as("Adding yoda").isEqualTo(1); + } + + @AfterTransaction + void afterTransaction() { + assertThatTransaction().isNotActive(); + this.inTransaction = false; + numAfterTransactionCalls++; + assertThat(deletePerson(jdbcTemplate, YODA)).as("Deleting yoda").isEqualTo(1); + assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table after a transactional test method.").isEqualTo(0); + } + + @Before + public void before() { + assertShouldBeInTransaction(); + long expected = (this.inTransaction ? 1 : 0); + assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table before a test method.").isEqualTo(expected); + } + + @After + public void after() { + assertShouldBeInTransaction(); + } + + @Test + public void transactionalMethod1() { + assertThatTransaction().isActive(); + assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1); + assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table within transactionalMethod1().").isEqualTo(2); + } + + @Test + public void transactionalMethod2() { + assertThatTransaction().isActive(); + assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1); + assertThat(addPerson(jdbcTemplate, SUE)).as("Adding sue").isEqualTo(1); + assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table within transactionalMethod2().").isEqualTo(3); + } + + @Test + @Transactional(propagation = Propagation.NOT_SUPPORTED) + public void nonTransactionalMethod() { + assertThatTransaction().isNotActive(); + assertThat(addPerson(jdbcTemplate, LUKE)).as("Adding luke").isEqualTo(1); + assertThat(addPerson(jdbcTemplate, LEIA)).as("Adding leia").isEqualTo(1); + assertThat(addPerson(jdbcTemplate, YODA)).as("Adding yoda").isEqualTo(1); + assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table without a transaction.").isEqualTo(3); + } + + + private void assertShouldBeInTransaction() { + boolean shouldBeInTransaction = !testName.getMethodName().equals("nonTransactionalMethod"); + if (shouldBeInTransaction) { + assertThatTransaction().isActive(); + } + else { + assertThatTransaction().isNotActive(); + } + } + + + + private static int clearPersonTable(JdbcTemplate jdbcTemplate) { + return jdbcTemplate.update("DELETE FROM person"); + } + + private static int countRowsInPersonTable(JdbcTemplate jdbcTemplate) { + return jdbcTemplate.queryForObject("SELECT COUNT(0) FROM person", Integer.class); + } + + private static int addPerson(JdbcTemplate jdbcTemplate, String name) { + return jdbcTemplate.update("INSERT INTO person VALUES(?)", name); + } + + private static int deletePerson(JdbcTemplate jdbcTemplate, String name) { + return jdbcTemplate.update("DELETE FROM person WHERE name=?", name); + } } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/integration/MockitoBeanAndSpringMethodRuleWithRepeatJUnit4IntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/rules/MockitoBeanAndSpringMethodRuleWithRepeatJUnit4IntegrationTests.java similarity index 92% rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/integration/MockitoBeanAndSpringMethodRuleWithRepeatJUnit4IntegrationTests.java rename to spring-test/src/test/java/org/springframework/test/context/junit4/rules/MockitoBeanAndSpringMethodRuleWithRepeatJUnit4IntegrationTests.java index 588e7ac01057..15211ba521a8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/integration/MockitoBeanAndSpringMethodRuleWithRepeatJUnit4IntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/rules/MockitoBeanAndSpringMethodRuleWithRepeatJUnit4IntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.bean.override.mockito.integration; +package org.springframework.test.context.junit4.rules; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -23,7 +23,6 @@ import org.springframework.test.annotation.Repeat; import org.springframework.test.context.bean.override.mockito.MockitoBean; -import org.springframework.test.context.junit4.rules.SpringMethodRule; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/Spr3896TestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/Spr3896TestSuite.java deleted file mode 100644 index 994251e8d799..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/Spr3896TestSuite.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2002-2019 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.junit4.spr3896; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -/** - * JUnit 4 based test suite for functionality proposed in SPR-3896. - * - * @author Sam Brannen - * @since 2.5 - */ -@RunWith(Suite.class) -// Note: the following 'multi-line' layout is for enhanced code readability. -@SuiteClasses({ - -DefaultLocationsBaseTests.class, - -DefaultLocationsInheritedTests.class, - -ExplicitLocationsBaseTests.class, - -ExplicitLocationsInheritedTests.class, - -BeanOverridingDefaultLocationsInheritedTests.class, - -BeanOverridingExplicitLocationsInheritedTests.class - -}) -public class Spr3896TestSuite { -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/Spr8849Tests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/Spr8849Tests.java deleted file mode 100644 index 707c726535de..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/Spr8849Tests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2002-2024 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.junit4.spr8849; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -/** - * Test suite to investigate claims raised in - * SPR-8849. - * - *

    Work Around

    - *

    By using a SpEL expression to generate a random {@code database-name} - * for the embedded database (see {@code datasource-config.xml}), we ensure - * that each {@code ApplicationContext} that imports the common configuration - * will create an embedded database with a unique name. - * - *

    To reproduce the problem mentioned in SPR-8849, delete the declaration - * of the {@code database-name} attribute of the embedded database in - * {@code datasource-config.xml} and run this suite. - * - *

    Solution

    - *

    As of Spring 4.2, a proper solution is possible thanks to SPR-8849. - * {@link TestClass3} and {@link TestClass4} both import - * {@code datasource-config-with-auto-generated-db-name.xml} which makes - * use of the new {@code generate-name} attribute of {@code }. - * - * @author Sam Brannen - * @since 3.2 - */ -@RunWith(Suite.class) -@SuiteClasses({ TestClass1.class, TestClass2.class, TestClass3.class, TestClass4.class }) -public class Spr8849Tests { - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass1.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass1.java deleted file mode 100644 index 9567ddd3b863..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass1.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2002-2019 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.junit4.spr8849; - -import javax.sql.DataSource; - -import jakarta.annotation.Resource; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ImportResource; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * This name of this class intentionally does not end with "Test" or "Tests" - * since it should only be run as part of the test suite: {@link Spr8849Tests}. - * - * @author Mickael Leduque - * @author Sam Brannen - * @since 3.2 - * @see Spr8849Tests - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration -public class TestClass1 { - - @Configuration - @ImportResource("classpath:/org/springframework/test/context/junit4/spr8849/datasource-config.xml") - static class Config { - } - - - @Resource - DataSource dataSource; - - - @Test - public void dummyTest() { - assertThat(dataSource).isNotNull(); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass2.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass2.java deleted file mode 100644 index 0cca42550432..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass2.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2002-2019 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.junit4.spr8849; - -import javax.sql.DataSource; - -import jakarta.annotation.Resource; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ImportResource; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * This name of this class intentionally does not end with "Test" or "Tests" - * since it should only be run as part of the test suite: {@link Spr8849Tests}. - * - * @author Mickael Leduque - * @author Sam Brannen - * @since 3.2 - * @see Spr8849Tests - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration -public class TestClass2 { - - @Configuration - @ImportResource("classpath:/org/springframework/test/context/junit4/spr8849/datasource-config.xml") - static class Config { - } - - - @Resource - DataSource dataSource; - - - @Test - public void dummyTest() { - assertThat(dataSource).isNotNull(); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass3.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass3.java deleted file mode 100644 index 1d978e8524e5..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass3.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2002-2019 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.junit4.spr8849; - -import javax.sql.DataSource; - -import jakarta.annotation.Resource; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ImportResource; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * This name of this class intentionally does not end with "Test" or "Tests" - * since it should only be run as part of the test suite: {@link Spr8849Tests}. - * - * @author Sam Brannen - * @since 4.2 - * @see Spr8849Tests - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration -public class TestClass3 { - - @Configuration - @ImportResource("classpath:/org/springframework/test/context/junit4/spr8849/datasource-config-with-auto-generated-db-name.xml") - static class Config { - } - - - @Resource - DataSource dataSource; - - - @Test - public void dummyTest() { - assertThat(dataSource).isNotNull(); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass4.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass4.java deleted file mode 100644 index 7a6a189549fc..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass4.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2002-2019 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.junit4.spr8849; - -import javax.sql.DataSource; - -import jakarta.annotation.Resource; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ImportResource; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * This name of this class intentionally does not end with "Test" or "Tests" - * since it should only be run as part of the test suite: {@link Spr8849Tests}. - * - * @author Sam Brannen - * @since 4.2 - * @see Spr8849Tests - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration -public class TestClass4 { - - @Configuration - @ImportResource("classpath:/org/springframework/test/context/junit4/spr8849/datasource-config-with-auto-generated-db-name.xml") - static class Config { - } - - - @Resource - DataSource dataSource; - - - @Test - public void dummyTest() { - assertThat(dataSource).isNotNull(); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/statements/SpringFailOnTimeoutTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/statements/SpringFailOnTimeoutTests.java index 4a8481f33b4a..e170ce12c6a7 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/statements/SpringFailOnTimeoutTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/statements/SpringFailOnTimeoutTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -43,22 +43,22 @@ public class SpringFailOnTimeoutTests { @Test public void nullNextStatement() { - assertThatIllegalArgumentException().isThrownBy(() -> - new SpringFailOnTimeout(null, 1)); + assertThatIllegalArgumentException() + .isThrownBy(() -> new SpringFailOnTimeout(null, 1)); } @Test public void negativeTimeout() { - assertThatIllegalArgumentException().isThrownBy(() -> - new SpringFailOnTimeout(statement, -1)); + assertThatIllegalArgumentException() + .isThrownBy(() -> new SpringFailOnTimeout(statement, -1)); } @Test public void userExceptionPropagates() throws Throwable { willThrow(new Boom()).given(statement).evaluate(); - assertThatExceptionOfType(Boom.class).isThrownBy(() -> - new SpringFailOnTimeout(statement, 1).evaluate()); + assertThatExceptionOfType(Boom.class) + .isThrownBy(() -> new SpringFailOnTimeout(statement, 1).evaluate()); } @Test @@ -68,8 +68,8 @@ public void timeoutExceptionThrownIfNoUserException() throws Throwable { return null; }).given(statement).evaluate(); - assertThatExceptionOfType(TimeoutException.class).isThrownBy(() -> - new SpringFailOnTimeout(statement, 1).evaluate()); + assertThatExceptionOfType(TimeoutException.class) + .isThrownBy(() -> new SpringFailOnTimeout(statement, 1).evaluate()); } @Test diff --git a/spring-test/src/test/java/org/springframework/test/context/web/JUnit4SpringContextWebTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/web/JUnit4SpringContextWebTests.java similarity index 95% rename from spring-test/src/test/java/org/springframework/test/context/web/JUnit4SpringContextWebTests.java rename to spring-test/src/test/java/org/springframework/test/context/junit4/web/JUnit4SpringContextWebTests.java index b512ceefdfea..170d7e39b4e3 100644 --- a/spring-test/src/test/java/org/springframework/test/context/web/JUnit4SpringContextWebTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/web/JUnit4SpringContextWebTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.web; +package org.springframework.test.context.junit4.web; import java.io.File; @@ -30,6 +30,7 @@ import org.springframework.mock.web.MockServletContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; +import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.web.context.ServletContextAware; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.ServletWebRequest; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AbstractTransactionalAnnotatedConfigClassTests.java b/spring-test/src/test/java/org/springframework/test/context/litemode/AbstractTransactionalAnnotatedConfigClassTests.java similarity index 81% rename from spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AbstractTransactionalAnnotatedConfigClassTests.java rename to spring-test/src/test/java/org/springframework/test/context/litemode/AbstractTransactionalAnnotatedConfigClassTests.java index 6489efd4a096..0a3b37ca6236 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AbstractTransactionalAnnotatedConfigClassTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/litemode/AbstractTransactionalAnnotatedConfigClassTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -14,14 +14,13 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.spr9051; +package org.springframework.test.context.litemode; import javax.sql.DataSource; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.testfixture.beans.Employee; @@ -29,7 +28,7 @@ import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.transaction.AfterTransaction; import org.springframework.test.context.transaction.BeforeTransaction; import org.springframework.transaction.annotation.Transactional; @@ -40,16 +39,16 @@ /** * This set of tests (i.e., all concrete subclasses) investigates the claims made in - * SPR-9051 + * gh-13690. * with regard to transactional tests. * * @author Sam Brannen * @since 3.2 * @see org.springframework.test.context.testng.AnnotationConfigTransactionalTestNGSpringContextTests */ -@RunWith(SpringJUnit4ClassRunner.class) +@SpringJUnitConfig @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) -public abstract class AbstractTransactionalAnnotatedConfigClassTests { +abstract class AbstractTransactionalAnnotatedConfigClassTests { protected static final String JANE = "jane"; protected static final String SUE = "sue"; @@ -65,12 +64,12 @@ public abstract class AbstractTransactionalAnnotatedConfigClassTests { @Autowired - public void setTransactionManager(DataSourceTransactionManager transactionManager) { + void setTransactionManager(DataSourceTransactionManager transactionManager) { this.dataSourceFromTxManager = transactionManager.getDataSource(); } @Autowired - public void setDataSource(DataSource dataSource) { + void setDataSource(DataSource dataSource) { this.dataSourceViaInjection = dataSource; this.jdbcTemplate = new JdbcTemplate(dataSource); } @@ -96,38 +95,38 @@ protected void assertAddPerson(final String name) { } @Test - public void autowiringFromConfigClass() { + void autowiringFromConfigClass() { assertThat(employee).as("The employee should have been autowired.").isNotNull(); assertThat(employee.getName()).isEqualTo("John Smith"); } @BeforeTransaction - public void beforeTransaction() { + void beforeTransaction() { assertNumRowsInPersonTable(0, "before a transactional test method"); assertAddPerson(YODA); } - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { assertNumRowsInPersonTable((isActualTransactionActive() ? 1 : 0), "before a test method"); } @Test @Transactional - public void modifyTestDataWithinTransaction() { + void modifyTestDataWithinTransaction() { assertThatTransaction().isActive(); assertAddPerson(JANE); assertAddPerson(SUE); assertNumRowsInPersonTable(3, "in modifyTestDataWithinTransaction()"); } - @After - public void tearDown() { + @AfterEach + void tearDown() { assertNumRowsInPersonTable((isActualTransactionActive() ? 3 : 0), "after a test method"); } @AfterTransaction - public void afterTransaction() { + void afterTransaction() { assertThat(deletePerson(YODA)).as("Deleting yoda").isEqualTo(1); assertNumRowsInPersonTable(0, "after a transactional test method"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AnnotatedConfigClassesWithoutAtConfigurationTests.java b/spring-test/src/test/java/org/springframework/test/context/litemode/AnnotatedConfigClassesWithoutAtConfigurationTests.java similarity index 78% rename from spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AnnotatedConfigClassesWithoutAtConfigurationTests.java rename to spring-test/src/test/java/org/springframework/test/context/litemode/AnnotatedConfigClassesWithoutAtConfigurationTests.java index 523c6c06eae9..39c510af42ff 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AnnotatedConfigClassesWithoutAtConfigurationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/litemode/AnnotatedConfigClassesWithoutAtConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -14,24 +14,22 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.spr9051; +package org.springframework.test.context.litemode; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; /** * This set of tests refutes the claims made in - * SPR-9051. + * gh-13690. * *

    The Claims: * @@ -48,9 +46,8 @@ * @author Phillip Webb * @since 3.2 */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = AnnotatedConfigClassesWithoutAtConfigurationTests.AnnotatedFactoryBeans.class) -public class AnnotatedConfigClassesWithoutAtConfigurationTests { +@SpringJUnitConfig(AnnotatedConfigClassesWithoutAtConfigurationTests.AnnotatedFactoryBeans.class) +class AnnotatedConfigClassesWithoutAtConfigurationTests { /** * This is intentionally not annotated with {@code @Configuration}. @@ -63,12 +60,12 @@ static class AnnotatedFactoryBeans { @Bean - public String enigma() { + String enigma() { return "enigma #" + enigmaCallCount.incrementAndGet(); } @Bean - public LifecycleBean lifecycleBean() { + LifecycleBean lifecycleBean() { // The following call to enigma() literally invokes the local // enigma() method, not a CGLIB proxied version, since these methods // are essentially factory bean methods. @@ -87,7 +84,7 @@ public LifecycleBean lifecycleBean() { @Test - public void testSPR_9051() { + void testSPR_9051() { assertThat(enigma).isNotNull(); assertThat(lifecycleBean).isNotNull(); assertThat(lifecycleBean.isInitialized()).isTrue(); diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AtBeanLiteModeScopeTests.java b/spring-test/src/test/java/org/springframework/test/context/litemode/AtBeanLiteModeScopeTests.java similarity index 76% rename from spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AtBeanLiteModeScopeTests.java rename to spring-test/src/test/java/org/springframework/test/context/litemode/AtBeanLiteModeScopeTests.java index 811ae9d07004..98557992be61 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AtBeanLiteModeScopeTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/litemode/AtBeanLiteModeScopeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,18 +14,16 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.spr9051; +package org.springframework.test.context.litemode; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Scope; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; @@ -36,9 +34,8 @@ * @author Sam Brannen * @since 3.2 */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = AtBeanLiteModeScopeTests.LiteBeans.class) -public class AtBeanLiteModeScopeTests { +@SpringJUnitConfig(AtBeanLiteModeScopeTests.LiteBeans.class) +class AtBeanLiteModeScopeTests { /** * This is intentionally not annotated with {@code @Configuration}. @@ -46,7 +43,7 @@ public class AtBeanLiteModeScopeTests { static class LiteBeans { @Bean - public LifecycleBean singleton() { + LifecycleBean singleton() { LifecycleBean bean = new LifecycleBean("singleton"); assertThat(bean.isInitialized()).isFalse(); return bean; @@ -54,7 +51,7 @@ public LifecycleBean singleton() { @Bean @Scope("prototype") - public LifecycleBean prototype() { + LifecycleBean prototype() { LifecycleBean bean = new LifecycleBean("prototype"); assertThat(bean.isInitialized()).isFalse(); return bean; @@ -63,19 +60,19 @@ public LifecycleBean prototype() { @Autowired - private ApplicationContext applicationContext; + ApplicationContext applicationContext; @Autowired @Qualifier("singleton") - private LifecycleBean injectedSingletonBean; + LifecycleBean injectedSingletonBean; @Autowired @Qualifier("prototype") - private LifecycleBean injectedPrototypeBean; + LifecycleBean injectedPrototypeBean; @Test - public void singletonLiteBean() { + void singletonLiteBean() { assertThat(injectedSingletonBean).isNotNull(); assertThat(injectedSingletonBean.isInitialized()).isTrue(); @@ -87,7 +84,7 @@ public void singletonLiteBean() { } @Test - public void prototypeLiteBean() { + void prototypeLiteBean() { assertThat(injectedPrototypeBean).isNotNull(); assertThat(injectedPrototypeBean.isInitialized()).isTrue(); diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/LifecycleBean.java b/spring-test/src/test/java/org/springframework/test/context/litemode/LifecycleBean.java similarity index 95% rename from spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/LifecycleBean.java rename to spring-test/src/test/java/org/springframework/test/context/litemode/LifecycleBean.java index 2a8c01156c2e..b5db34760488 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/LifecycleBean.java +++ b/spring-test/src/test/java/org/springframework/test/context/litemode/LifecycleBean.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.spr9051; +package org.springframework.test.context.litemode; import jakarta.annotation.PostConstruct; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/TransactionalAnnotatedConfigClassWithAtConfigurationTests.java b/spring-test/src/test/java/org/springframework/test/context/litemode/TransactionalAnnotatedConfigClassWithAtConfigurationTests.java similarity index 78% rename from spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/TransactionalAnnotatedConfigClassWithAtConfigurationTests.java rename to spring-test/src/test/java/org/springframework/test/context/litemode/TransactionalAnnotatedConfigClassWithAtConfigurationTests.java index 3a8df08203d2..3cac87b00024 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/TransactionalAnnotatedConfigClassWithAtConfigurationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/litemode/TransactionalAnnotatedConfigClassWithAtConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -14,11 +14,11 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.spr9051; +package org.springframework.test.context.litemode; import javax.sql.DataSource; -import org.junit.Before; +import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.testfixture.beans.Employee; import org.springframework.context.annotation.Bean; @@ -39,7 +39,7 @@ * @see TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests */ @ContextConfiguration -public class TransactionalAnnotatedConfigClassWithAtConfigurationTests extends +class TransactionalAnnotatedConfigClassWithAtConfigurationTests extends AbstractTransactionalAnnotatedConfigClassTests { /** @@ -52,7 +52,7 @@ public class TransactionalAnnotatedConfigClassWithAtConfigurationTests extends static class Config { @Bean - public Employee employee() { + Employee employee() { Employee employee = new Employee(); employee.setName("John Smith"); employee.setAge(42); @@ -61,24 +61,24 @@ public Employee employee() { } @Bean - public PlatformTransactionManager transactionManager() { + PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } @Bean - public DataSource dataSource() { + DataSource dataSource() { return new EmbeddedDatabaseBuilder()// - .addScript("classpath:/org/springframework/test/jdbc/schema.sql")// - // Ensure that this in-memory database is only used by this class: - .setName(getClass().getName())// - .build(); + .addScript("classpath:/org/springframework/test/jdbc/schema.sql")// + // Ensure that this in-memory database is only used by this class: + .setName(getClass().getName())// + .build(); } } - @Before - public void compareDataSources() { + @BeforeEach + void compareDataSources() { // NOTE: the two DataSource instances ARE the same! assertThat(dataSourceViaInjection).isSameAs(dataSourceFromTxManager); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests.java b/spring-test/src/test/java/org/springframework/test/context/litemode/TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests.java similarity index 88% rename from spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests.java rename to spring-test/src/test/java/org/springframework/test/context/litemode/TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests.java index 7914ac6aff97..d522bc9743ff 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/litemode/TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -14,11 +14,11 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.spr9051; +package org.springframework.test.context.litemode; import javax.sql.DataSource; -import org.junit.Before; +import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.testfixture.beans.Employee; import org.springframework.context.annotation.Bean; @@ -45,7 +45,7 @@ * @see TransactionalAnnotatedConfigClassWithAtConfigurationTests */ @ContextConfiguration(classes = TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests.AnnotatedFactoryBeans.class) -public class TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests extends +class TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests extends AbstractTransactionalAnnotatedConfigClassTests { /** @@ -58,7 +58,7 @@ public class TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests exte static class AnnotatedFactoryBeans { @Bean - public Employee employee() { + Employee employee() { Employee employee = new Employee(); employee.setName("John Smith"); employee.setAge(42); @@ -67,7 +67,7 @@ public Employee employee() { } @Bean - public PlatformTransactionManager transactionManager() { + PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } @@ -92,19 +92,19 @@ public PlatformTransactionManager transactionManager() { * which is almost certainly not the desired or intended behavior. */ @Bean - public DataSource dataSource() { + DataSource dataSource() { return new EmbeddedDatabaseBuilder()// - .addScript("classpath:/org/springframework/test/jdbc/schema.sql")// - // Ensure that this in-memory database is only used by this class: - .setName(getClass().getName())// - .build(); + .addScript("classpath:/org/springframework/test/jdbc/schema.sql")// + // Ensure that this in-memory database is only used by this class: + .setName(getClass().getName())// + .build(); } } - @Before - public void compareDataSources() { + @BeforeEach + void compareDataSources() { // NOTE: the two DataSource instances are NOT the same! assertThat(dataSourceViaInjection).isNotSameAs(dataSourceFromTxManager); } @@ -119,7 +119,7 @@ public void compareDataSources() { */ @AfterTransaction @Override - public void afterTransaction() { + void afterTransaction() { assertThat(deletePerson(YODA)).as("Deleting yoda").isEqualTo(1); // NOTE: We would actually expect that there are now ZERO entries in the diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests.java b/spring-test/src/test/java/org/springframework/test/context/orm/hibernate/HibernateSessionFlushingTests.java similarity index 71% rename from spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests.java rename to spring-test/src/test/java/org/springframework/test/context/orm/hibernate/HibernateSessionFlushingTests.java index 54f2177dc955..239f8f910fc3 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/orm/hibernate/HibernateSessionFlushingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -14,21 +14,24 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.orm; +package org.springframework.test.context.orm.hibernate; + +import javax.sql.DataSource; import jakarta.persistence.PersistenceException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.exception.ConstraintViolationException; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; -import org.springframework.test.context.junit4.orm.domain.DriversLicense; -import org.springframework.test.context.junit4.orm.domain.Person; -import org.springframework.test.context.junit4.orm.service.PersonService; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.orm.hibernate.domain.DriversLicense; +import org.springframework.test.context.orm.hibernate.domain.Person; +import org.springframework.test.context.orm.hibernate.service.PersonService; +import org.springframework.test.jdbc.JdbcTestUtils; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; @@ -43,23 +46,32 @@ * @author Juergen Hoeller * @author Vlad Mihalcea * @since 3.0 - * @see org.springframework.test.context.junit.jupiter.orm.JpaEntityListenerTests + * @see org.springframework.test.context.orm.jpa.JpaEntityListenerTests */ -@ContextConfiguration -public class HibernateSessionFlushingTests extends AbstractTransactionalJUnit4SpringContextTests { +@SpringJUnitConfig +@Transactional +class HibernateSessionFlushingTests { private static final String SAM = "Sam"; private static final String JUERGEN = "Juergen"; + JdbcTemplate jdbcTemplate; + + @Autowired + SessionFactory sessionFactory; + @Autowired - private PersonService personService; + PersonService personService; + @Autowired - private SessionFactory sessionFactory; + void setDataSource(DataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } - @Before - public void setup() { + @BeforeEach + void setup() { assertThatTransaction().isActive(); assertThat(personService).as("PersonService should have been autowired.").isNotNull(); assertThat(sessionFactory).as("SessionFactory should have been autowired.").isNotNull(); @@ -67,7 +79,7 @@ public void setup() { @Test - public void findSam() { + void findSam() { Person sam = personService.findByName(SAM); assertThat(sam).as("Should be able to find Sam").isNotNull(); DriversLicense driversLicense = sam.getDriversLicense(); @@ -77,7 +89,7 @@ public void findSam() { @Test // SPR-16956 @Transactional(readOnly = true) - public void findSamWithReadOnlySession() { + void findSamWithReadOnlySession() { Person sam = personService.findByName(SAM); sam.setName("Vlad"); // By setting setDefaultReadOnly(true), the user can no longer modify any entity... @@ -88,7 +100,7 @@ public void findSamWithReadOnlySession() { } @Test - public void saveJuergenWithDriversLicense() { + void saveJuergenWithDriversLicense() { DriversLicense driversLicense = new DriversLicense(2L, 2222L); Person juergen = new Person(JUERGEN, driversLicense); int numRows = countRowsInTable("person"); @@ -99,21 +111,21 @@ public void saveJuergenWithDriversLicense() { } @Test - public void saveJuergenWithNullDriversLicense() { - assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> - personService.save(new Person(JUERGEN))); + void saveJuergenWithNullDriversLicense() { + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> personService.save(new Person(JUERGEN))); } @Test // no expected exception! - public void updateSamWithNullDriversLicenseWithoutSessionFlush() { + void updateSamWithNullDriversLicenseWithoutSessionFlush() { updateSamWithNullDriversLicense(); // False positive, since an exception will be thrown once the session is // finally flushed (i.e., in production code) } @Test - public void updateSamWithNullDriversLicenseWithSessionFlush() { + void updateSamWithNullDriversLicenseWithSessionFlush() { updateSamWithNullDriversLicense(); assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> { // Manual flush is required to avoid false positive in test @@ -134,4 +146,8 @@ private void updateSamWithNullDriversLicense() { personService.save(sam); } + private int countRowsInTable(String tableName) { + return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName); + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/DriversLicense.java b/spring-test/src/test/java/org/springframework/test/context/orm/hibernate/domain/DriversLicense.java similarity index 94% rename from spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/DriversLicense.java rename to spring-test/src/test/java/org/springframework/test/context/orm/hibernate/domain/DriversLicense.java index 8f3d9df05915..905ab6c2fbe4 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/DriversLicense.java +++ b/spring-test/src/test/java/org/springframework/test/context/orm/hibernate/domain/DriversLicense.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.orm.domain; +package org.springframework.test.context.orm.hibernate.domain; /** * DriversLicense POJO. diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/Person.java b/spring-test/src/test/java/org/springframework/test/context/orm/hibernate/domain/Person.java similarity index 96% rename from spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/Person.java rename to spring-test/src/test/java/org/springframework/test/context/orm/hibernate/domain/Person.java index e0348fd1bc88..0168c6719a3b 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/Person.java +++ b/spring-test/src/test/java/org/springframework/test/context/orm/hibernate/domain/Person.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.orm.domain; +package org.springframework.test.context.orm.hibernate.domain; /** * Person POJO. diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/hibernate/HibernatePersonRepository.java b/spring-test/src/test/java/org/springframework/test/context/orm/hibernate/repository/HibernatePersonRepository.java similarity index 65% rename from spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/hibernate/HibernatePersonRepository.java rename to spring-test/src/test/java/org/springframework/test/context/orm/hibernate/repository/HibernatePersonRepository.java index 317ec5a5fdc2..66f9cb03dd5e 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/hibernate/HibernatePersonRepository.java +++ b/spring-test/src/test/java/org/springframework/test/context/orm/hibernate/repository/HibernatePersonRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2025 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. @@ -14,14 +14,12 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.orm.repository.hibernate; +package org.springframework.test.context.orm.hibernate.repository; import org.hibernate.SessionFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; -import org.springframework.test.context.junit4.orm.domain.Person; -import org.springframework.test.context.junit4.orm.repository.PersonRepository; +import org.springframework.test.context.orm.hibernate.domain.Person; /** * Hibernate implementation of the {@link PersonRepository} API. @@ -30,12 +28,11 @@ * @since 3.0 */ @Repository -public class HibernatePersonRepository implements PersonRepository { +class HibernatePersonRepository implements PersonRepository { private final SessionFactory sessionFactory; - @Autowired public HibernatePersonRepository(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } @@ -48,8 +45,10 @@ public Person save(Person person) { @Override public Person findByName(String name) { - return (Person) this.sessionFactory.getCurrentSession().createQuery( - "from Person person where person.name = :name").setParameter("name", name).getSingleResult(); + return (Person) this.sessionFactory.getCurrentSession() + .createQuery("from Person person where person.name = :name") + .setParameter("name", name) + .getSingleResult(); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/PersonRepository.java b/spring-test/src/test/java/org/springframework/test/context/orm/hibernate/repository/PersonRepository.java similarity index 85% rename from spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/PersonRepository.java rename to spring-test/src/test/java/org/springframework/test/context/orm/hibernate/repository/PersonRepository.java index b75f511f9377..7dfc668081b7 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/PersonRepository.java +++ b/spring-test/src/test/java/org/springframework/test/context/orm/hibernate/repository/PersonRepository.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.orm.repository; +package org.springframework.test.context.orm.hibernate.repository; -import org.springframework.test.context.junit4.orm.domain.Person; +import org.springframework.test.context.orm.hibernate.domain.Person; /** * Person Repository API. diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/service/PersonService.java b/spring-test/src/test/java/org/springframework/test/context/orm/hibernate/service/PersonService.java similarity index 85% rename from spring-test/src/test/java/org/springframework/test/context/junit4/orm/service/PersonService.java rename to spring-test/src/test/java/org/springframework/test/context/orm/hibernate/service/PersonService.java index d42b03602e1b..3334fce555b8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/service/PersonService.java +++ b/spring-test/src/test/java/org/springframework/test/context/orm/hibernate/service/PersonService.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.orm.service; +package org.springframework.test.context.orm.hibernate.service; -import org.springframework.test.context.junit4.orm.domain.Person; +import org.springframework.test.context.orm.hibernate.domain.Person; /** * Person Service API. diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/service/impl/StandardPersonService.java b/spring-test/src/test/java/org/springframework/test/context/orm/hibernate/service/StandardPersonService.java similarity index 72% rename from spring-test/src/test/java/org/springframework/test/context/junit4/orm/service/impl/StandardPersonService.java rename to spring-test/src/test/java/org/springframework/test/context/orm/hibernate/service/StandardPersonService.java index 2e98308a19d2..da9df33b85a7 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/service/impl/StandardPersonService.java +++ b/spring-test/src/test/java/org/springframework/test/context/orm/hibernate/service/StandardPersonService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2025 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. @@ -14,13 +14,11 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.orm.service.impl; +package org.springframework.test.context.orm.hibernate.service; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.test.context.junit4.orm.domain.Person; -import org.springframework.test.context.junit4.orm.repository.PersonRepository; -import org.springframework.test.context.junit4.orm.service.PersonService; +import org.springframework.test.context.orm.hibernate.domain.Person; +import org.springframework.test.context.orm.hibernate.repository.PersonRepository; import org.springframework.transaction.annotation.Transactional; /** @@ -31,12 +29,11 @@ */ @Service @Transactional(readOnly = true) -public class StandardPersonService implements PersonService { +class StandardPersonService implements PersonService { private final PersonRepository personRepository; - @Autowired public StandardPersonService(PersonRepository personRepository) { this.personRepository = personRepository; } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/JpaEntityListenerTests.java b/spring-test/src/test/java/org/springframework/test/context/orm/jpa/JpaEntityListenerTests.java similarity index 92% rename from spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/JpaEntityListenerTests.java rename to spring-test/src/test/java/org/springframework/test/context/orm/jpa/JpaEntityListenerTests.java index 0056da8df692..2b6eb5adea11 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/JpaEntityListenerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/orm/jpa/JpaEntityListenerTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit.jupiter.orm; +package org.springframework.test.context.orm.jpa; import java.util.List; @@ -37,10 +37,10 @@ import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.test.context.junit.jupiter.orm.domain.JpaPersonRepository; -import org.springframework.test.context.junit.jupiter.orm.domain.Person; -import org.springframework.test.context.junit.jupiter.orm.domain.PersonListener; -import org.springframework.test.context.junit.jupiter.orm.domain.PersonRepository; +import org.springframework.test.context.orm.jpa.domain.JpaPersonRepository; +import org.springframework.test.context.orm.jpa.domain.Person; +import org.springframework.test.context.orm.jpa.domain.PersonListener; +import org.springframework.test.context.orm.jpa.domain.PersonRepository; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.Transactional; @@ -53,7 +53,7 @@ * @author Sam Brannen * @since 5.3.18 * @see issue gh-28228 - * @see org.springframework.test.context.junit4.orm.HibernateSessionFlushingTests + * @see org.springframework.test.context.orm.hibernate.HibernateSessionFlushingTests */ @SpringJUnitConfig @Transactional diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/domain/JpaPersonRepository.java b/spring-test/src/test/java/org/springframework/test/context/orm/jpa/domain/JpaPersonRepository.java similarity index 95% rename from spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/domain/JpaPersonRepository.java rename to spring-test/src/test/java/org/springframework/test/context/orm/jpa/domain/JpaPersonRepository.java index eb5e38973ead..fdbdfcdbd50d 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/domain/JpaPersonRepository.java +++ b/spring-test/src/test/java/org/springframework/test/context/orm/jpa/domain/JpaPersonRepository.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit.jupiter.orm.domain; +package org.springframework.test.context.orm.jpa.domain; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/domain/Person.java b/spring-test/src/test/java/org/springframework/test/context/orm/jpa/domain/Person.java similarity index 95% rename from spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/domain/Person.java rename to spring-test/src/test/java/org/springframework/test/context/orm/jpa/domain/Person.java index 1a67dc439b5b..e2b77c8161ad 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/domain/Person.java +++ b/spring-test/src/test/java/org/springframework/test/context/orm/jpa/domain/Person.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit.jupiter.orm.domain; +package org.springframework.test.context.orm.jpa.domain; import jakarta.persistence.Entity; import jakarta.persistence.EntityListeners; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/domain/PersonListener.java b/spring-test/src/test/java/org/springframework/test/context/orm/jpa/domain/PersonListener.java similarity index 96% rename from spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/domain/PersonListener.java rename to spring-test/src/test/java/org/springframework/test/context/orm/jpa/domain/PersonListener.java index 1ba3dce8b6f6..0e4752ce565f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/domain/PersonListener.java +++ b/spring-test/src/test/java/org/springframework/test/context/orm/jpa/domain/PersonListener.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit.jupiter.orm.domain; +package org.springframework.test.context.orm.jpa.domain; import java.util.ArrayList; import java.util.List; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/domain/PersonRepository.java b/spring-test/src/test/java/org/springframework/test/context/orm/jpa/domain/PersonRepository.java similarity index 92% rename from spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/domain/PersonRepository.java rename to spring-test/src/test/java/org/springframework/test/context/orm/jpa/domain/PersonRepository.java index 9576a649d0fa..b981db1198be 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/domain/PersonRepository.java +++ b/spring-test/src/test/java/org/springframework/test/context/orm/jpa/domain/PersonRepository.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit.jupiter.orm.domain; +package org.springframework.test.context.orm.jpa.domain; /** * Person repository API. diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DefaultProfileAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/profile/annotation/DefaultProfileAnnotationConfigTests.java similarity index 65% rename from spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DefaultProfileAnnotationConfigTests.java rename to spring-test/src/test/java/org/springframework/test/context/profile/annotation/DefaultProfileAnnotationConfigTests.java index 6e6fed937dc1..70b9f1532fc2 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DefaultProfileAnnotationConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/profile/annotation/DefaultProfileAnnotationConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,16 +14,14 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.profile.annotation; +package org.springframework.test.context.profile.annotation; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.testfixture.beans.Employee; import org.springframework.beans.testfixture.beans.Pet; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.support.AnnotationConfigContextLoader; import static org.assertj.core.api.Assertions.assertThat; @@ -32,25 +30,24 @@ * @author Sam Brannen * @since 3.1 */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = { DefaultProfileConfig.class, DevProfileConfig.class }, loader = AnnotationConfigContextLoader.class) -public class DefaultProfileAnnotationConfigTests { +@SpringJUnitConfig(classes = { DefaultProfileConfig.class, DevProfileConfig.class }, loader = AnnotationConfigContextLoader.class) +class DefaultProfileAnnotationConfigTests { @Autowired - protected Pet pet; + Pet pet; @Autowired(required = false) - protected Employee employee; + Employee employee; @Test - public void pet() { + void pet() { assertThat(pet).isNotNull(); assertThat(pet.getName()).isEqualTo("Fido"); } @Test - public void employee() { + void employee() { assertThat(employee).as("employee bean should not be created for the default profile").isNull(); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DefaultProfileConfig.java b/spring-test/src/test/java/org/springframework/test/context/profile/annotation/DefaultProfileConfig.java similarity index 90% rename from spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DefaultProfileConfig.java rename to spring-test/src/test/java/org/springframework/test/context/profile/annotation/DefaultProfileConfig.java index 67fefb771999..f5ade7012312 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DefaultProfileConfig.java +++ b/spring-test/src/test/java/org/springframework/test/context/profile/annotation/DefaultProfileConfig.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.profile.annotation; +package org.springframework.test.context.profile.annotation; import org.springframework.beans.testfixture.beans.Pet; import org.springframework.context.annotation.Bean; @@ -24,7 +24,7 @@ * @author Sam Brannen * @since 3.1 */ -@Configuration +@Configuration(proxyBeanMethods = false) public class DefaultProfileConfig { @Bean diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/profile/annotation/DevProfileAnnotationConfigTests.java similarity index 77% rename from spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileAnnotationConfigTests.java rename to spring-test/src/test/java/org/springframework/test/context/profile/annotation/DevProfileAnnotationConfigTests.java index 36aa83b11470..7587d24a8b15 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileAnnotationConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/profile/annotation/DevProfileAnnotationConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.profile.annotation; +package org.springframework.test.context.profile.annotation; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -27,11 +27,11 @@ * @since 3.1 */ @ActiveProfiles("dev") -public class DevProfileAnnotationConfigTests extends DefaultProfileAnnotationConfigTests { +class DevProfileAnnotationConfigTests extends DefaultProfileAnnotationConfigTests { @Test @Override - public void employee() { + void employee() { assertThat(employee).as("employee bean should be loaded for the 'dev' profile").isNotNull(); assertThat(employee.getName()).isEqualTo("John Smith"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileConfig.java b/spring-test/src/test/java/org/springframework/test/context/profile/annotation/DevProfileConfig.java similarity index 94% rename from spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileConfig.java rename to spring-test/src/test/java/org/springframework/test/context/profile/annotation/DevProfileConfig.java index c8a964ce7acd..a662ee16a60c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileConfig.java +++ b/spring-test/src/test/java/org/springframework/test/context/profile/annotation/DevProfileConfig.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.profile.annotation; +package org.springframework.test.context.profile.annotation; import org.springframework.beans.testfixture.beans.Employee; import org.springframework.context.annotation.Bean; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileResolverAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/profile/annotation/DevProfileResolverAnnotationConfigTests.java similarity index 85% rename from spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileResolverAnnotationConfigTests.java rename to spring-test/src/test/java/org/springframework/test/context/profile/annotation/DevProfileResolverAnnotationConfigTests.java index a913abaf6ef1..d27534dab9df 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileResolverAnnotationConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/profile/annotation/DevProfileResolverAnnotationConfigTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.profile.importresource; +package org.springframework.test.context.profile.annotation; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfilesResolver; @@ -24,7 +24,7 @@ * @since 4.0 */ @ActiveProfiles(resolver = DevProfileResolverAnnotationConfigTests.class, inheritProfiles = false) -public class DevProfileResolverAnnotationConfigTests extends DevProfileAnnotationConfigTests implements +class DevProfileResolverAnnotationConfigTests extends DevProfileAnnotationConfigTests implements ActiveProfilesResolver { @Override diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/profile/importresource/DefaultProfileAnnotationConfigTests.java similarity index 66% rename from spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java rename to spring-test/src/test/java/org/springframework/test/context/profile/importresource/DefaultProfileAnnotationConfigTests.java index 73b57ef93d19..bfd2fc735445 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/profile/importresource/DefaultProfileAnnotationConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,16 +14,14 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.profile.importresource; +package org.springframework.test.context.profile.importresource; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.testfixture.beans.Employee; import org.springframework.beans.testfixture.beans.Pet; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; @@ -31,25 +29,24 @@ * @author Juergen Hoeller * @since 3.1 */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = DefaultProfileConfig.class) -public class DefaultProfileAnnotationConfigTests { +@SpringJUnitConfig(DefaultProfileConfig.class) +class DefaultProfileAnnotationConfigTests { @Autowired - protected Pet pet; + Pet pet; @Autowired(required = false) - protected Employee employee; + Employee employee; @Test - public void pet() { + void pet() { assertThat(pet).isNotNull(); assertThat(pet.getName()).isEqualTo("Fido"); } @Test - public void employee() { + void employee() { assertThat(employee).as("employee bean should not be created for the default profile").isNull(); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileConfig.java b/spring-test/src/test/java/org/springframework/test/context/profile/importresource/DefaultProfileConfig.java similarity index 83% rename from spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileConfig.java rename to spring-test/src/test/java/org/springframework/test/context/profile/importresource/DefaultProfileConfig.java index f07511b8943c..24a11e09fb9f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileConfig.java +++ b/spring-test/src/test/java/org/springframework/test/context/profile/importresource/DefaultProfileConfig.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.profile.importresource; +package org.springframework.test.context.profile.importresource; import org.springframework.beans.testfixture.beans.Pet; import org.springframework.context.annotation.Bean; @@ -25,8 +25,8 @@ * @author Juergen Hoeller * @since 3.1 */ -@Configuration -@ImportResource("org/springframework/test/context/junit4/profile/importresource/import.xml") +@Configuration(proxyBeanMethods = false) +@ImportResource("org/springframework/test/context/profile/importresource/import.xml") public class DefaultProfileConfig { @Bean diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/profile/importresource/DevProfileAnnotationConfigTests.java similarity index 77% rename from spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileAnnotationConfigTests.java rename to spring-test/src/test/java/org/springframework/test/context/profile/importresource/DevProfileAnnotationConfigTests.java index 0eb24078af11..553be1ee6390 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileAnnotationConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/profile/importresource/DevProfileAnnotationConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.profile.importresource; +package org.springframework.test.context.profile.importresource; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -27,11 +27,11 @@ * @since 3.1 */ @ActiveProfiles("dev") -public class DevProfileAnnotationConfigTests extends DefaultProfileAnnotationConfigTests { +class DevProfileAnnotationConfigTests extends DefaultProfileAnnotationConfigTests { @Test @Override - public void employee() { + void employee() { assertThat(employee).as("employee bean should be loaded for the 'dev' profile").isNotNull(); assertThat(employee.getName()).isEqualTo("John Smith"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileResolverAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/profile/importresource/DevProfileResolverAnnotationConfigTests.java similarity index 94% rename from spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileResolverAnnotationConfigTests.java rename to spring-test/src/test/java/org/springframework/test/context/profile/importresource/DevProfileResolverAnnotationConfigTests.java index 9fe028dc7abb..b388184555a8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileResolverAnnotationConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/profile/importresource/DevProfileResolverAnnotationConfigTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.profile.annotation; +package org.springframework.test.context.profile.importresource; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfilesResolver; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolver.java b/spring-test/src/test/java/org/springframework/test/context/profile/resolver/ClassNameActiveProfilesResolver.java similarity index 73% rename from spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolver.java rename to spring-test/src/test/java/org/springframework/test/context/profile/resolver/ClassNameActiveProfilesResolver.java index d352b5a5a4b0..f2a8e699f9b8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolver.java +++ b/spring-test/src/test/java/org/springframework/test/context/profile/resolver/ClassNameActiveProfilesResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2025 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.profile.resolver; +package org.springframework.test.context.profile.resolver; import org.springframework.test.context.ActiveProfilesResolver; @@ -22,10 +22,11 @@ * @author Michail Nikolaev * @since 4.0 */ -public class ClassNameActiveProfilesResolver implements ActiveProfilesResolver { +class ClassNameActiveProfilesResolver implements ActiveProfilesResolver { @Override public String[] resolve(Class testClass) { - return new String[] { testClass.getSimpleName().toLowerCase() }; + return new String[] { testClass.getSimpleName() }; } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/profile/resolver/ClassNameActiveProfilesResolverTests.java b/spring-test/src/test/java/org/springframework/test/context/profile/resolver/ClassNameActiveProfilesResolverTests.java new file mode 100644 index 000000000000..4c4c87d2dc48 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/profile/resolver/ClassNameActiveProfilesResolverTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2025 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.profile.resolver; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Michail Nikolaev + * @author Sam Brannen + * @since 4.0 + */ +@SpringJUnitConfig +@ActiveProfiles(resolver = ClassNameActiveProfilesResolver.class) +class ClassNameActiveProfilesResolverTests { + + @Test + void test(@Autowired Environment environment) { + assertThat(environment.getActiveProfiles()).contains(getClass().getSimpleName()); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/profile/xml/DefaultProfileXmlConfigTests.java similarity index 68% rename from spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests.java rename to spring-test/src/test/java/org/springframework/test/context/profile/xml/DefaultProfileXmlConfigTests.java index d9effe2223ff..14ccf3775f64 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/profile/xml/DefaultProfileXmlConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,16 +14,14 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.profile.xml; +package org.springframework.test.context.profile.xml; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.testfixture.beans.Employee; import org.springframework.beans.testfixture.beans.Pet; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; @@ -31,25 +29,24 @@ * @author Sam Brannen * @since 3.1 */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration -public class DefaultProfileXmlConfigTests { +@SpringJUnitConfig +class DefaultProfileXmlConfigTests { @Autowired - protected Pet pet; + Pet pet; @Autowired(required = false) - protected Employee employee; + Employee employee; @Test - public void pet() { + void pet() { assertThat(pet).isNotNull(); assertThat(pet.getName()).isEqualTo("Fido"); } @Test - public void employee() { + void employee() { assertThat(employee).as("employee bean should not be created for the default profile").isNull(); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileResolverXmlConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/profile/xml/DevProfileResolverXmlConfigTests.java similarity index 84% rename from spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileResolverXmlConfigTests.java rename to spring-test/src/test/java/org/springframework/test/context/profile/xml/DevProfileResolverXmlConfigTests.java index ef30225d9c40..7453f36fa4b3 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileResolverXmlConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/profile/xml/DevProfileResolverXmlConfigTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.profile.xml; +package org.springframework.test.context.profile.xml; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfilesResolver; @@ -24,7 +24,7 @@ * @since 4.0 */ @ActiveProfiles(resolver = DevProfileResolverXmlConfigTests.class, inheritProfiles = false) -public class DevProfileResolverXmlConfigTests extends DevProfileXmlConfigTests implements ActiveProfilesResolver { +class DevProfileResolverXmlConfigTests extends DevProfileXmlConfigTests implements ActiveProfilesResolver { @Override public String[] resolve(Class testClass) { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileXmlConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/profile/xml/DevProfileXmlConfigTests.java similarity index 79% rename from spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileXmlConfigTests.java rename to spring-test/src/test/java/org/springframework/test/context/profile/xml/DevProfileXmlConfigTests.java index b79ef637ed53..854e9c706658 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileXmlConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/profile/xml/DevProfileXmlConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.profile.xml; +package org.springframework.test.context.profile.xml; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -27,11 +27,11 @@ * @since 3.1 */ @ActiveProfiles("dev") -public class DevProfileXmlConfigTests extends DefaultProfileXmlConfigTests { +class DevProfileXmlConfigTests extends DefaultProfileXmlConfigTests { @Test @Override - public void employee() { + void employee() { assertThat(employee).as("employee bean should be loaded for the 'dev' profile").isNotNull(); assertThat(employee.getName()).isEqualTo("John Smith"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsTestNGTests.java b/spring-test/src/test/java/org/springframework/test/context/testng/FailingBeforeAndAfterMethodsTestNGTests.java similarity index 57% rename from spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsTestNGTests.java rename to spring-test/src/test/java/org/springframework/test/context/testng/FailingBeforeAndAfterMethodsTestNGTests.java index cec9ed5296a5..8aae38f36694 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsTestNGTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/testng/FailingBeforeAndAfterMethodsTestNGTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 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. @@ -14,27 +14,25 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.testng; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import java.util.List; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.testng.TestNG; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestContext; import org.springframework.test.context.TestExecutionListener; import org.springframework.test.context.TestExecutionListeners; -import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; -import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests; -import org.springframework.test.context.testng.TrackingTestNGTestListener; import org.springframework.test.context.transaction.AfterTransaction; import org.springframework.test.context.transaction.BeforeTransaction; -import org.springframework.util.ClassUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.params.provider.Arguments.argumentSet; /** * Integration tests which verify that 'before' and 'after' @@ -50,62 +48,40 @@ * @author Sam Brannen * @since 2.5 */ -@RunWith(Parameterized.class) -public class FailingBeforeAndAfterMethodsTestNGTests { - - protected final Class clazz; - - protected final int expectedTestStartCount; - - protected final int expectedTestSuccessCount; +class FailingBeforeAndAfterMethodsTestNGTests { - protected final int expectedFailureCount; - - protected final int expectedFailedConfigurationsCount; - - - @Parameters(name = "{0}") - public static Object[][] testData() { - return new Object[][] { - { AlwaysFailingBeforeTestClassTestCase.class.getSimpleName(), 1, 0, 0, 1 }, - { AlwaysFailingAfterTestClassTestCase.class.getSimpleName(), 1, 1, 0, 1 }, - { AlwaysFailingPrepareTestInstanceTestCase.class.getSimpleName(), 1, 0, 0, 1 }, - { AlwaysFailingBeforeTestMethodTestCase.class.getSimpleName(), 1, 0, 0, 1 }, - { AlwaysFailingBeforeTestExecutionTestCase.class.getSimpleName(), 1, 0, 1, 0 }, - { AlwaysFailingAfterTestExecutionTestCase.class.getSimpleName(), 1, 0, 1, 0 }, - { AlwaysFailingAfterTestMethodTestCase.class.getSimpleName(), 1, 1, 0, 1 }, - { FailingBeforeTransactionTestCase.class.getSimpleName(), 1, 0, 0, 1 }, - { FailingAfterTransactionTestCase.class.getSimpleName(), 1, 1, 0, 1 } - }; - } - - - public FailingBeforeAndAfterMethodsTestNGTests(String testClassName, int expectedTestStartCount, + @ParameterizedTest + @MethodSource("testData") + void runTestAndAssertCounters(Class clazz, int expectedTestStartCount, int expectedTestSuccessCount, int expectedFailureCount, int expectedFailedConfigurationsCount) throws Exception { - this.clazz = ClassUtils.forName(getClass().getName() + "." + testClassName, getClass().getClassLoader()); - this.expectedTestStartCount = expectedTestStartCount; - this.expectedTestSuccessCount = expectedTestSuccessCount; - this.expectedFailureCount = expectedFailureCount; - this.expectedFailedConfigurationsCount = expectedFailedConfigurationsCount; - } - - - @Test - public void runTestAndAssertCounters() { TrackingTestNGTestListener listener = new TrackingTestNGTestListener(); TestNG testNG = new TestNG(); testNG.addListener(listener); - testNG.setTestClasses(new Class[] {this.clazz}); + testNG.setTestClasses(new Class[] {clazz}); testNG.setVerbose(0); testNG.run(); - String name = this.clazz.getSimpleName(); + String name = clazz.getSimpleName(); + + assertThat(listener.testStartCount).as("tests started for [" + name + "] ==> ").isEqualTo(expectedTestStartCount); + assertThat(listener.testSuccessCount).as("successful tests for [" + name + "] ==> ").isEqualTo(expectedTestSuccessCount); + assertThat(listener.testFailureCount).as("failed tests for [" + name + "] ==> ").isEqualTo(expectedFailureCount); + assertThat(listener.failedConfigurationsCount).as("failed configurations for [" + name + "] ==> ").isEqualTo(expectedFailedConfigurationsCount); + } - assertThat(listener.testStartCount).as("tests started for [" + name + "] ==> ").isEqualTo(this.expectedTestStartCount); - assertThat(listener.testSuccessCount).as("successful tests for [" + name + "] ==> ").isEqualTo(this.expectedTestSuccessCount); - assertThat(listener.testFailureCount).as("failed tests for [" + name + "] ==> ").isEqualTo(this.expectedFailureCount); - assertThat(listener.failedConfigurationsCount).as("failed configurations for [" + name + "] ==> ").isEqualTo(this.expectedFailedConfigurationsCount); + static List testData() { + return List.of( + argumentSet("AlwaysFailingBeforeTestClass", AlwaysFailingBeforeTestClassTestCase.class, 1, 0, 0, 1), + argumentSet("AlwaysFailingAfterTestClass", AlwaysFailingAfterTestClassTestCase.class, 1, 1, 0, 1), + argumentSet("AlwaysFailingPrepareTestInstance", AlwaysFailingPrepareTestInstanceTestCase.class, 1, 0, 0, 1), + argumentSet("AlwaysFailingBeforeTestMethod", AlwaysFailingBeforeTestMethodTestCase.class, 1, 0, 0, 1), + argumentSet("AlwaysFailingBeforeTestExecution", AlwaysFailingBeforeTestExecutionTestCase.class, 1, 0, 1, 0), + argumentSet("AlwaysFailingAfterTestExecution", AlwaysFailingAfterTestExecutionTestCase.class, 1, 0, 1, 0), + argumentSet("AlwaysFailingAfterTestMethod", AlwaysFailingAfterTestMethodTestCase.class, 1, 1, 0, 1), + argumentSet("FailingBeforeTransaction", FailingBeforeTransactionTestCase.class, 1, 0, 0, 1), + argumentSet("FailingAfterTransaction", FailingAfterTransactionTestCase.class, 1, 1, 0, 1) + ); } @@ -167,63 +143,63 @@ public void afterTestMethod(TestContext testContext) { @TestExecutionListeners(inheritListeners = false) - public abstract static class BaseTestCase extends AbstractTestNGSpringContextTests { + abstract static class BaseTestCase extends AbstractTestNGSpringContextTests { @org.testng.annotations.Test - public void testNothing() { + void testNothing() { } } @TestExecutionListeners(AlwaysFailingBeforeTestClassTestExecutionListener.class) - public static class AlwaysFailingBeforeTestClassTestCase extends BaseTestCase { + static class AlwaysFailingBeforeTestClassTestCase extends BaseTestCase { } @TestExecutionListeners(AlwaysFailingAfterTestClassTestExecutionListener.class) - public static class AlwaysFailingAfterTestClassTestCase extends BaseTestCase { + static class AlwaysFailingAfterTestClassTestCase extends BaseTestCase { } @TestExecutionListeners(AlwaysFailingPrepareTestInstanceTestExecutionListener.class) - public static class AlwaysFailingPrepareTestInstanceTestCase extends BaseTestCase { + static class AlwaysFailingPrepareTestInstanceTestCase extends BaseTestCase { } @TestExecutionListeners(AlwaysFailingBeforeTestMethodTestExecutionListener.class) - public static class AlwaysFailingBeforeTestMethodTestCase extends BaseTestCase { + static class AlwaysFailingBeforeTestMethodTestCase extends BaseTestCase { } @TestExecutionListeners(AlwaysFailingBeforeTestExecutionTestExecutionListener.class) - public static class AlwaysFailingBeforeTestExecutionTestCase extends BaseTestCase { + static class AlwaysFailingBeforeTestExecutionTestCase extends BaseTestCase { } @TestExecutionListeners(AlwaysFailingAfterTestExecutionTestExecutionListener.class) - public static class AlwaysFailingAfterTestExecutionTestCase extends BaseTestCase { + static class AlwaysFailingAfterTestExecutionTestCase extends BaseTestCase { } @TestExecutionListeners(AlwaysFailingAfterTestMethodTestExecutionListener.class) - public static class AlwaysFailingAfterTestMethodTestCase extends BaseTestCase { + static class AlwaysFailingAfterTestMethodTestCase extends BaseTestCase { } @ContextConfiguration("FailingBeforeAndAfterMethodsTests-context.xml") - public static class FailingBeforeTransactionTestCase extends AbstractTransactionalTestNGSpringContextTests { + static class FailingBeforeTransactionTestCase extends AbstractTransactionalTestNGSpringContextTests { @org.testng.annotations.Test - public void testNothing() { + void testNothing() { } @BeforeTransaction - public void beforeTransaction() { + void beforeTransaction() { fail("always failing beforeTransaction()"); } } @ContextConfiguration("FailingBeforeAndAfterMethodsTests-context.xml") - public static class FailingAfterTransactionTestCase extends AbstractTransactionalTestNGSpringContextTests { + static class FailingAfterTransactionTestCase extends AbstractTransactionalTestNGSpringContextTests { @org.testng.annotations.Test - public void testNothing() { + void testNothing() { } @AfterTransaction - public void afterTransaction() { + void afterTransaction() { fail("always failing afterTransaction()"); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/AbstractTransactionalSpringTests.java similarity index 74% rename from spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java rename to spring-test/src/test/java/org/springframework/test/context/transaction/AbstractTransactionalSpringTests.java index bc49ad416d36..c9654ec9e5ca 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/transaction/AbstractTransactionalSpringTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2025 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. @@ -14,27 +14,24 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; - -import org.junit.runner.RunWith; +package org.springframework.test.context.transaction; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.transaction.annotation.Transactional; /** - * Abstract base class for verifying support of Spring's {@link Transactional - * @Transactional} annotation. + * Abstract base class for verifying support of Spring's + * {@link Transactional @Transactional} annotation. * * @author Sam Brannen * @since 2.5 - * @see ClassLevelTransactionalSpringRunnerTests - * @see MethodLevelTransactionalSpringRunnerTests + * @see ClassLevelTransactionalSpringTests + * @see MethodLevelTransactionalSpringTests * @see Transactional */ -@RunWith(SpringRunner.class) -@ContextConfiguration("transactionalTests-context.xml") -public abstract class AbstractTransactionalSpringRunnerTests { +@SpringJUnitConfig(locations = "transactionalTests-context.xml") +abstract class AbstractTransactionalSpringTests { protected static final String BOB = "bob"; protected static final String JANE = "jane"; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/BeforeAndAfterTransactionAnnotationTests.java similarity index 75% rename from spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests.java rename to spring-test/src/test/java/org/springframework/test/context/transaction/BeforeAndAfterTransactionAnnotationTests.java index 8c2b877ecbeb..75ce73c0e71f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/transaction/BeforeAndAfterTransactionAnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 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. @@ -14,22 +14,21 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.transaction; import javax.sql.DataSource; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.test.context.transaction.AfterTransaction; -import org.springframework.test.context.transaction.BeforeTransaction; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -37,7 +36,7 @@ import static org.springframework.test.transaction.TransactionAssert.assertThatTransaction; /** - * JUnit 4 based integration test which verifies + * JUnit based integration test which verifies * {@link BeforeTransaction @BeforeTransaction} and * {@link AfterTransaction @AfterTransaction} behavior. * @@ -45,33 +44,31 @@ * @since 2.5 */ @Transactional -public class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactionalSpringRunnerTests { +@TestInstance(Lifecycle.PER_CLASS) +class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactionalSpringTests { - protected static JdbcTemplate jdbcTemplate; + private static int numBeforeTransactionCalls = 0; + private static int numAfterTransactionCalls = 0; - protected static int numBeforeTransactionCalls = 0; - protected static int numAfterTransactionCalls = 0; + JdbcTemplate jdbcTemplate; - protected boolean inTransaction = false; - - @Rule - public final TestName testName = new TestName(); + boolean inTransaction = false; @Autowired - public void setDataSource(DataSource dataSource) { + void setDataSource(DataSource dataSource) { jdbcTemplate = new JdbcTemplate(dataSource); } - @BeforeClass - public static void beforeClass() { + @BeforeAll + void beforeClass() { BeforeAndAfterTransactionAnnotationTests.numBeforeTransactionCalls = 0; BeforeAndAfterTransactionAnnotationTests.numAfterTransactionCalls = 0; } - @AfterClass - public static void afterClass() { + @AfterAll + void afterClass() { assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(3); assertThat(BeforeAndAfterTransactionAnnotationTests.numBeforeTransactionCalls).as("Verifying the total number of calls to beforeTransaction().").isEqualTo(2); assertThat(BeforeAndAfterTransactionAnnotationTests.numAfterTransactionCalls).as("Verifying the total number of calls to afterTransaction().").isEqualTo(2); @@ -95,37 +92,27 @@ void afterTransaction() { assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table after a transactional test method.").isEqualTo(0); } - @Before - public void before() { - assertShouldBeInTransaction(); + @BeforeEach + void before(TestInfo testInfo) { + assertShouldBeInTransaction(testInfo); long expected = (this.inTransaction ? 1 : 0); assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table before a test method.").isEqualTo(expected); } - private void assertShouldBeInTransaction() { - boolean shouldBeInTransaction = !testName.getMethodName().equals("nonTransactionalMethod"); - if (shouldBeInTransaction) { - assertThatTransaction().isActive(); - } - else { - assertThatTransaction().isNotActive(); - } - } - - @After - public void after() { - assertShouldBeInTransaction(); + @AfterEach + void after(TestInfo testInfo) { + assertShouldBeInTransaction(testInfo); } @Test - public void transactionalMethod1() { + void transactionalMethod1() { assertThatTransaction().isActive(); assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1); assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table within transactionalMethod1().").isEqualTo(2); } @Test - public void transactionalMethod2() { + void transactionalMethod2() { assertThatTransaction().isActive(); assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1); assertThat(addPerson(jdbcTemplate, SUE)).as("Adding sue").isEqualTo(1); @@ -134,7 +121,7 @@ public void transactionalMethod2() { @Test @Transactional(propagation = Propagation.NOT_SUPPORTED) - public void nonTransactionalMethod() { + void nonTransactionalMethod() { assertThatTransaction().isNotActive(); assertThat(addPerson(jdbcTemplate, LUKE)).as("Adding luke").isEqualTo(1); assertThat(addPerson(jdbcTemplate, LEIA)).as("Adding leia").isEqualTo(1); @@ -142,4 +129,14 @@ public void nonTransactionalMethod() { assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table without a transaction.").isEqualTo(3); } + + private static void assertShouldBeInTransaction(TestInfo testInfo) { + if (!testInfo.getTestMethod().get().getName().equals("nonTransactionalMethod")) { + assertThatTransaction().isActive(); + } + else { + assertThatTransaction().isNotActive(); + } + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/ClassLevelTransactionalSpringTests.java similarity index 73% rename from spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests.java rename to spring-test/src/test/java/org/springframework/test/context/transaction/ClassLevelTransactionalSpringTests.java index a72d3064971d..973cbdf50653 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/transaction/ClassLevelTransactionalSpringTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,13 +14,15 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.transaction; import javax.sql.DataSource; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; @@ -29,7 +31,6 @@ import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; -import org.springframework.test.context.transaction.TransactionalTestExecutionListener; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -37,12 +38,11 @@ import static org.springframework.test.transaction.TransactionAssert.assertThatTransaction; /** - * JUnit 4 based integration test which verifies support of Spring's - * {@link Transactional @Transactional}, {@link TestExecutionListeners - * @TestExecutionListeners}, and {@link ContextConfiguration - * @ContextConfiguration} annotations in conjunction with the - * {@link SpringRunner} and the following - * {@link TestExecutionListener TestExecutionListeners}: + * JUnit based integration test which verifies support of Spring's + * {@link Transactional @Transactional}, + * {@link TestExecutionListeners @TestExecutionListeners}, and + * {@link ContextConfiguration @ContextConfiguration} annotations in conjunction + * with the following {@link TestExecutionListener TestExecutionListeners}: * *

      *
    • {@link DependencyInjectionTestExecutionListener}
    • @@ -55,33 +55,29 @@ * * @author Sam Brannen * @since 2.5 - * @see MethodLevelTransactionalSpringRunnerTests + * @see MethodLevelTransactionalSpringTests */ @Transactional -public class ClassLevelTransactionalSpringRunnerTests extends AbstractTransactionalSpringRunnerTests { +@TestInstance(Lifecycle.PER_CLASS) +class ClassLevelTransactionalSpringTests extends AbstractTransactionalSpringTests { - protected static JdbcTemplate jdbcTemplate; + private JdbcTemplate jdbcTemplate; @Autowired - public void setDataSource(DataSource dataSource) { + void setDataSource(DataSource dataSource) { jdbcTemplate = new JdbcTemplate(dataSource); } - @AfterClass - public static void verifyFinalTestData() { - assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(4); - } - - @Before - public void verifyInitialTestData() { + @BeforeEach + void verifyInitialTestData() { clearPersonTable(jdbcTemplate); assertThat(addPerson(jdbcTemplate, BOB)).as("Adding bob").isEqualTo(1); assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the initial number of rows in the person table.").isEqualTo(1); } @Test - public void modifyTestDataWithinTransaction() { + void modifyTestDataWithinTransaction() { assertThatTransaction().isActive(); assertThat(deletePerson(jdbcTemplate, BOB)).as("Deleting bob").isEqualTo(1); assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1); @@ -91,7 +87,7 @@ public void modifyTestDataWithinTransaction() { @Test @Transactional(propagation = Propagation.NOT_SUPPORTED) - public void modifyTestDataWithoutTransaction() { + void modifyTestDataWithoutTransaction() { assertThatTransaction().isNotActive(); assertThat(addPerson(jdbcTemplate, LUKE)).as("Adding luke").isEqualTo(1); assertThat(addPerson(jdbcTemplate, LEIA)).as("Adding leia").isEqualTo(1); @@ -99,4 +95,10 @@ public void modifyTestDataWithoutTransaction() { assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table without a transaction.").isEqualTo(4); } + @AfterAll + void verifyFinalTestData() { + assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(4); + } + } + diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseRollbackAnnotationTransactionalTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/DefaultRollbackFalseRollbackAnnotationTransactionalTests.java similarity index 71% rename from spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseRollbackAnnotationTransactionalTests.java rename to spring-test/src/test/java/org/springframework/test/context/transaction/DefaultRollbackFalseRollbackAnnotationTransactionalTests.java index 01606bc62df3..8584b4492dae 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseRollbackAnnotationTransactionalTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/transaction/DefaultRollbackFalseRollbackAnnotationTransactionalTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,19 +14,21 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.transaction; + import javax.sql.DataSource; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.annotation.Rollback; -import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; @@ -43,32 +45,32 @@ * @since 4.2 * @see Rollback * @see Transactional#transactionManager - * @see DefaultRollbackFalseTransactionalTests + * @see DefaultRollbackTrueRollbackAnnotationTransactionalTests */ -@RunWith(SpringRunner.class) -@ContextConfiguration(classes = EmbeddedPersonDatabaseTestsConfig.class, inheritLocations = false) +@SpringJUnitConfig(classes = EmbeddedPersonDatabaseTestsConfig.class, inheritLocations = false) @Transactional("txMgr") @Rollback(false) -public class DefaultRollbackFalseRollbackAnnotationTransactionalTests extends AbstractTransactionalSpringRunnerTests { +@TestInstance(Lifecycle.PER_CLASS) +class DefaultRollbackFalseRollbackAnnotationTransactionalTests extends AbstractTransactionalSpringTests { - private static JdbcTemplate jdbcTemplate; + JdbcTemplate jdbcTemplate; @Autowired - public void setDataSource(DataSource dataSource) { + void setDataSource(DataSource dataSource) { jdbcTemplate = new JdbcTemplate(dataSource); } - @Before - public void verifyInitialTestData() { + @BeforeEach + void verifyInitialTestData() { clearPersonTable(jdbcTemplate); assertThat(addPerson(jdbcTemplate, BOB)).as("Adding bob").isEqualTo(1); assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the initial number of rows in the person table.").isEqualTo(1); } @Test - public void modifyTestDataWithinTransaction() { + void modifyTestDataWithinTransaction() { assertThatTransaction().isActive(); assertThat(deletePerson(jdbcTemplate, BOB)).as("Deleting bob").isEqualTo(1); assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1); @@ -76,8 +78,8 @@ public void modifyTestDataWithinTransaction() { assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table within a transaction.").isEqualTo(2); } - @AfterClass - public static void verifyFinalTestData() { + @AfterAll + void verifyFinalTestData() { assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(2); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueRollbackAnnotationTransactionalTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/DefaultRollbackTrueRollbackAnnotationTransactionalTests.java similarity index 70% rename from spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueRollbackAnnotationTransactionalTests.java rename to spring-test/src/test/java/org/springframework/test/context/transaction/DefaultRollbackTrueRollbackAnnotationTransactionalTests.java index 280ee36bd41d..c9bffd68e6b7 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueRollbackAnnotationTransactionalTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/transaction/DefaultRollbackTrueRollbackAnnotationTransactionalTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,19 +14,21 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.transaction; import javax.sql.DataSource; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.Timeout; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.annotation.Rollback; -import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; @@ -43,42 +45,43 @@ * @since 4.2 * @see Rollback * @see Transactional#transactionManager - * @see DefaultRollbackTrueTransactionalTests + * @see DefaultRollbackFalseRollbackAnnotationTransactionalTests */ -@RunWith(SpringRunner.class) -@ContextConfiguration(classes = EmbeddedPersonDatabaseTestsConfig.class, inheritLocations = false) +@SpringJUnitConfig(classes = EmbeddedPersonDatabaseTestsConfig.class, inheritLocations = false) @Transactional("txMgr") @Rollback(true) -public class DefaultRollbackTrueRollbackAnnotationTransactionalTests extends AbstractTransactionalSpringRunnerTests { +@TestInstance(Lifecycle.PER_CLASS) +class DefaultRollbackTrueRollbackAnnotationTransactionalTests extends AbstractTransactionalSpringTests { private static int originalNumRows; - private static JdbcTemplate jdbcTemplate; + JdbcTemplate jdbcTemplate; @Autowired - public void setDataSource(DataSource dataSource) { + void setDataSource(DataSource dataSource) { jdbcTemplate = new JdbcTemplate(dataSource); } - @Before - public void verifyInitialTestData() { + @BeforeEach + void verifyInitialTestData() { originalNumRows = clearPersonTable(jdbcTemplate); assertThat(addPerson(jdbcTemplate, BOB)).as("Adding bob").isEqualTo(1); assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the initial number of rows in the person table.").isEqualTo(1); } - @Test(timeout = 1000) - public void modifyTestDataWithinTransaction() { + @Test + @Timeout(1) + void modifyTestDataWithinTransaction() { assertThatTransaction().isActive(); assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1); assertThat(addPerson(jdbcTemplate, SUE)).as("Adding sue").isEqualTo(1); assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table within a transaction.").isEqualTo(3); } - @AfterClass - public static void verifyFinalTestData() { + @AfterAll + void verifyFinalTestData() { assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(originalNumRows); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/EmbeddedPersonDatabaseTestsConfig.java b/spring-test/src/test/java/org/springframework/test/context/transaction/EmbeddedPersonDatabaseTestsConfig.java similarity index 85% rename from spring-test/src/test/java/org/springframework/test/context/junit4/EmbeddedPersonDatabaseTestsConfig.java rename to spring-test/src/test/java/org/springframework/test/context/transaction/EmbeddedPersonDatabaseTestsConfig.java index 3ea1f029e0c7..d74b2b983ecb 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/EmbeddedPersonDatabaseTestsConfig.java +++ b/spring-test/src/test/java/org/springframework/test/context/transaction/EmbeddedPersonDatabaseTestsConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.transaction; import javax.sql.DataSource; @@ -32,15 +32,15 @@ * @since 4.2 */ @Configuration -public class EmbeddedPersonDatabaseTestsConfig { +class EmbeddedPersonDatabaseTestsConfig { @Bean - public PlatformTransactionManager txMgr() { + PlatformTransactionManager txMgr() { return new DataSourceTransactionManager(dataSource()); } @Bean - public DataSource dataSource() { + DataSource dataSource() { return new EmbeddedDatabaseBuilder() .generateUniqueName(true) .addScript("classpath:/org/springframework/test/jdbc/schema.sql") diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/MethodLevelTransactionalSpringTests.java similarity index 74% rename from spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java rename to spring-test/src/test/java/org/springframework/test/context/transaction/MethodLevelTransactionalSpringTests.java index 1c959ac67122..f47bc83a131c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/transaction/MethodLevelTransactionalSpringTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,13 +14,15 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.transaction; import javax.sql.DataSource; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -30,19 +32,17 @@ import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; -import org.springframework.test.context.transaction.TransactionalTestExecutionListener; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.transaction.TransactionAssert.assertThatTransaction; /** - * JUnit 4 based integration test which verifies support of Spring's - * {@link Transactional @Transactional}, {@link TestExecutionListeners - * @TestExecutionListeners}, and {@link ContextConfiguration - * @ContextConfiguration} annotations in conjunction with the - * {@link SpringRunner} and the following - * {@link TestExecutionListener TestExecutionListeners}: + * JUnit based integration test which verifies support of Spring's + * {@link Transactional @Transactional}, + * {@link TestExecutionListeners @TestExecutionListeners}, and + * {@link ContextConfiguration @ContextConfiguration} annotations in conjunction + * with the following {@link TestExecutionListener TestExecutionListeners}: * *
        *
      • {@link DependencyInjectionTestExecutionListener}
      • @@ -55,28 +55,24 @@ * * @author Sam Brannen * @since 2.5 - * @see ClassLevelTransactionalSpringRunnerTests + * @see ClassLevelTransactionalSpringTests */ @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class }) -public class MethodLevelTransactionalSpringRunnerTests extends AbstractTransactionalSpringRunnerTests { +@TestInstance(Lifecycle.PER_CLASS) +class MethodLevelTransactionalSpringTests extends AbstractTransactionalSpringTests { - protected static JdbcTemplate jdbcTemplate; + private JdbcTemplate jdbcTemplate; @Autowired @Qualifier("dataSource2") - public void setDataSource(DataSource dataSource) { + void setDataSource(DataSource dataSource) { jdbcTemplate = new JdbcTemplate(dataSource); } - @AfterClass - public static void verifyFinalTestData() { - assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(4); - } - - @Before - public void verifyInitialTestData() { + @BeforeEach + void verifyInitialTestData() { clearPersonTable(jdbcTemplate); assertThat(addPerson(jdbcTemplate, BOB)).as("Adding bob").isEqualTo(1); assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the initial number of rows in the person table.").isEqualTo(1); @@ -84,7 +80,7 @@ public void verifyInitialTestData() { @Test @Transactional("transactionManager2") - public void modifyTestDataWithinTransaction() { + void modifyTestDataWithinTransaction() { assertThatTransaction().isActive(); assertThat(deletePerson(jdbcTemplate, BOB)).as("Deleting bob").isEqualTo(1); assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1); @@ -93,7 +89,7 @@ public void modifyTestDataWithinTransaction() { } @Test - public void modifyTestDataWithoutTransaction() { + void modifyTestDataWithoutTransaction() { assertThatTransaction().isNotActive(); assertThat(addPerson(jdbcTemplate, LUKE)).as("Adding luke").isEqualTo(1); assertThat(addPerson(jdbcTemplate, LEIA)).as("Adding leia").isEqualTo(1); @@ -101,4 +97,10 @@ public void modifyTestDataWithoutTransaction() { assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table without a transaction.").isEqualTo(4); } + @AfterAll + void verifyFinalTestData() { + assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(4); + } + } + diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackFalseTransactionalTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/RollbackOverrideDefaultRollbackFalseTransactionalTests.java similarity index 74% rename from spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackFalseTransactionalTests.java rename to spring-test/src/test/java/org/springframework/test/context/transaction/RollbackOverrideDefaultRollbackFalseTransactionalTests.java index deb69f9ea474..57cbbc7eaad8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackFalseTransactionalTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/transaction/RollbackOverrideDefaultRollbackFalseTransactionalTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,16 +14,12 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.transaction; -import javax.sql.DataSource; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.annotation.Rollback; import static org.assertj.core.api.Assertions.assertThat; @@ -38,23 +34,15 @@ * @since 2.5 * @see Rollback */ -public class RollbackOverrideDefaultRollbackFalseTransactionalTests +class RollbackOverrideDefaultRollbackFalseTransactionalTests extends DefaultRollbackFalseRollbackAnnotationTransactionalTests { private static int originalNumRows; - private static JdbcTemplate jdbcTemplate; - - - @Autowired - @Override - public void setDataSource(DataSource dataSource) { - jdbcTemplate = new JdbcTemplate(dataSource); - } - @Before + @BeforeEach @Override - public void verifyInitialTestData() { + void verifyInitialTestData() { originalNumRows = clearPersonTable(jdbcTemplate); assertThat(addPerson(jdbcTemplate, BOB)).as("Adding bob").isEqualTo(1); assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the initial number of rows in the person table.").isEqualTo(1); @@ -63,7 +51,7 @@ public void verifyInitialTestData() { @Test @Rollback @Override - public void modifyTestDataWithinTransaction() { + void modifyTestDataWithinTransaction() { assertThatTransaction().isActive(); assertThat(deletePerson(jdbcTemplate, BOB)).as("Deleting bob").isEqualTo(1); assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1); @@ -71,8 +59,9 @@ public void modifyTestDataWithinTransaction() { assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table within a transaction.").isEqualTo(2); } - @AfterClass - public static void verifyFinalTestData() { + @AfterAll + @Override + void verifyFinalTestData() { assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(originalNumRows); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackTrueTransactionalTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/RollbackOverrideDefaultRollbackTrueTransactionalTests.java similarity index 72% rename from spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackTrueTransactionalTests.java rename to spring-test/src/test/java/org/springframework/test/context/transaction/RollbackOverrideDefaultRollbackTrueTransactionalTests.java index 3661ceca9f65..422f4875ce62 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackTrueTransactionalTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/transaction/RollbackOverrideDefaultRollbackTrueTransactionalTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -14,16 +14,12 @@ * limitations under the License. */ -package org.springframework.test.context.junit4; +package org.springframework.test.context.transaction; -import javax.sql.DataSource; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.annotation.Rollback; import static org.assertj.core.api.Assertions.assertThat; @@ -38,21 +34,12 @@ * @since 2.5 * @see Rollback */ -public class RollbackOverrideDefaultRollbackTrueTransactionalTests +class RollbackOverrideDefaultRollbackTrueTransactionalTests extends DefaultRollbackTrueRollbackAnnotationTransactionalTests { - private static JdbcTemplate jdbcTemplate; - - + @BeforeEach @Override - @Autowired - public void setDataSource(DataSource dataSource) { - jdbcTemplate = new JdbcTemplate(dataSource); - } - - @Before - @Override - public void verifyInitialTestData() { + void verifyInitialTestData() { clearPersonTable(jdbcTemplate); assertThat(addPerson(jdbcTemplate, BOB)).as("Adding bob").isEqualTo(1); assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the initial number of rows in the person table.").isEqualTo(1); @@ -61,15 +48,16 @@ public void verifyInitialTestData() { @Test @Rollback(false) @Override - public void modifyTestDataWithinTransaction() { + void modifyTestDataWithinTransaction() { assertThatTransaction().isActive(); assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1); assertThat(addPerson(jdbcTemplate, SUE)).as("Adding sue").isEqualTo(1); assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table within a transaction.").isEqualTo(3); } - @AfterClass - public static void verifyFinalTestData() { + @AfterAll + @Override + void verifyFinalTestData() { assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(3); } diff --git a/spring-test/src/test/java/org/springframework/test/context/web/AbstractBasicWacTests.java b/spring-test/src/test/java/org/springframework/test/context/web/AbstractBasicWacTests.java index 70d7aca3197b..0aefc619943a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/web/AbstractBasicWacTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/web/AbstractBasicWacTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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. @@ -19,15 +19,13 @@ import java.io.File; import jakarta.servlet.ServletContext; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockServletContext; -import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.context.ServletContextAware; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.ServletWebRequest; @@ -38,7 +36,6 @@ * @author Sam Brannen * @since 3.2 */ -@RunWith(SpringRunner.class) @WebAppConfiguration public abstract class AbstractBasicWacTests implements ServletContextAware { @@ -72,7 +69,7 @@ public void setServletContext(ServletContext servletContext) { } @Test - public void basicWacFeatures() throws Exception { + protected void basicWacFeatures() throws Exception { assertThat(wac.getServletContext()).as("ServletContext should be set in the WAC.").isNotNull(); assertThat(servletContext).as("ServletContext should have been set via ServletContextAware.").isNotNull(); @@ -90,7 +87,6 @@ public void basicWacFeatures() throws Exception { assertThat(request.getServletContext()).as("ServletContext in the WAC and in the mock request").isSameAs(mockServletContext); assertThat(mockServletContext.getRealPath("index.jsp")).as("Getting real path for ServletContext resource.").isEqualTo(new File("src/main/webapp/index.jsp").getCanonicalPath()); - } } diff --git a/spring-test/src/test/java/org/springframework/test/context/web/BasicAnnotationConfigWacTests.java b/spring-test/src/test/java/org/springframework/test/context/web/BasicAnnotationConfigWacTests.java index d0c72e11e6a6..ec0d9a6f13cb 100644 --- a/spring-test/src/test/java/org/springframework/test/context/web/BasicAnnotationConfigWacTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/web/BasicAnnotationConfigWacTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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,12 +16,12 @@ package org.springframework.test.context.web; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; @@ -29,35 +29,37 @@ * @author Sam Brannen * @since 3.2 */ -@ContextConfiguration -public class BasicAnnotationConfigWacTests extends AbstractBasicWacTests { +@SpringJUnitConfig +class BasicAnnotationConfigWacTests extends AbstractBasicWacTests { - @Configuration + @Autowired + ServletContextAwareBean servletContextAwareBean; + + @Test + void fooEnigmaAutowired() { + assertThat(foo).isEqualTo("enigma"); + } + + @Test + void servletContextAwareBeanProcessed() { + assertThat(servletContextAwareBean).isNotNull(); + assertThat(servletContextAwareBean.getServletContext()).isNotNull(); + } + + + @Configuration(proxyBeanMethods = false) static class Config { @Bean - public String foo() { + String foo() { return "enigma"; } @Bean - public ServletContextAwareBean servletContextAwareBean() { + ServletContextAwareBean servletContextAwareBean() { return new ServletContextAwareBean(); } - } - - @Autowired - protected ServletContextAwareBean servletContextAwareBean; - - @Test - public void fooEnigmaAutowired() { - assertThat(foo).isEqualTo("enigma"); - } - @Test - public void servletContextAwareBeanProcessed() { - assertThat(servletContextAwareBean).isNotNull(); - assertThat(servletContextAwareBean.servletContext).isNotNull(); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/web/BasicGroovyWacTests.java b/spring-test/src/test/java/org/springframework/test/context/web/BasicGroovyWacTests.java index 69239d026ca2..980e5ca6ed6c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/web/BasicGroovyWacTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/web/BasicGroovyWacTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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,9 +16,11 @@ package org.springframework.test.context.web; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; @@ -28,11 +30,12 @@ * @see BasicXmlWacTests */ // Config loaded from BasicGroovyWacTestsContext.groovy +@ExtendWith(SpringExtension.class) @ContextConfiguration -public class BasicGroovyWacTests extends AbstractBasicWacTests { +class BasicGroovyWacTests extends AbstractBasicWacTests { @Test - public void groovyFooAutowired() { + void groovyFooAutowired() { assertThat(foo).isEqualTo("Groovy Foo"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/web/BasicXmlWacTests.java b/spring-test/src/test/java/org/springframework/test/context/web/BasicXmlWacTests.java index c1e9ecb368c5..2ff6b3cbf4e1 100644 --- a/spring-test/src/test/java/org/springframework/test/context/web/BasicXmlWacTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/web/BasicXmlWacTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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,21 +16,26 @@ package org.springframework.test.context.web; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; /** * @author Sam Brannen * @since 3.2 + * @see BasicGroovyWacTests */ +// Config loaded from BasicXmlWacTests-context.xml +@ExtendWith(SpringExtension.class) @ContextConfiguration -public class BasicXmlWacTests extends AbstractBasicWacTests { +class BasicXmlWacTests extends AbstractBasicWacTests { @Test - public void fooBarAutowired() { + void fooBarAutowired() { assertThat(foo).isEqualTo("bar"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9799/Spr9799AnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/web/EnableWebMvcAnnotationConfigTests.java similarity index 57% rename from spring-test/src/test/java/org/springframework/test/context/junit4/spr9799/Spr9799AnnotationConfigTests.java rename to spring-test/src/test/java/org/springframework/test/context/web/EnableWebMvcAnnotationConfigTests.java index 91c809c3dc8b..d99426870adc 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9799/Spr9799AnnotationConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/web/EnableWebMvcAnnotationConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2025 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. @@ -14,42 +14,45 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.spr9799; +package org.springframework.test.context.web; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig; +import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import static org.assertj.core.api.Assertions.assertThat; + /** * Integration tests used to assess claims raised in - * SPR-9799. + * gh-14432. * * @author Sam Brannen * @since 3.2 - * @see Spr9799XmlConfigTests + * @see EnableWebMvcXmlConfigTests */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration +@SpringJUnitWebConfig // NOTE: if we omit the @WebAppConfiguration declaration, the ApplicationContext will fail // to load since @EnableWebMvc requires that the context be a WebApplicationContext. -@WebAppConfiguration -public class Spr9799AnnotationConfigTests { +class EnableWebMvcAnnotationConfigTests extends AbstractBasicWacTests { + + @Test + void applicationContextLoads(WebApplicationContext wac) { + assertThat(wac.getBean("foo", String.class)).isEqualTo("enigma"); + } + @Configuration @EnableWebMvc static class Config { - /* intentionally no beans defined */ - } - - @Test - public void applicationContextLoads() { - // no-op + @Bean + String foo() { + return "enigma"; + } } } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9799/Spr9799XmlConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/web/EnableWebMvcXmlConfigTests.java similarity index 50% rename from spring-test/src/test/java/org/springframework/test/context/junit4/spr9799/Spr9799XmlConfigTests.java rename to spring-test/src/test/java/org/springframework/test/context/web/EnableWebMvcXmlConfigTests.java index 37c7f9338475..89545f0003e2 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9799/Spr9799XmlConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/web/EnableWebMvcXmlConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2025 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. @@ -14,30 +14,29 @@ * limitations under the License. */ -package org.springframework.test.context.junit4.spr9799; +package org.springframework.test.context.web; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig; +import org.springframework.web.context.WebApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests used to assess claims raised in - * SPR-9799. + * gh-14432. * * @author Sam Brannen * @since 3.2 - * @see Spr9799AnnotationConfigTests + * @see EnableWebMvcAnnotationConfigTests */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration -public class Spr9799XmlConfigTests { +@SpringJUnitWebConfig +class EnableWebMvcXmlConfigTests extends AbstractBasicWacTests { @Test - public void applicationContextLoads() { - // nothing to assert: we just want to make sure that the context loads without - // errors. + void applicationContextLoads(WebApplicationContext wac) { + assertThat(wac.getBean("foo", String.class)).isEqualTo("enigma"); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/web/ServletContextAwareBean.java b/spring-test/src/test/java/org/springframework/test/context/web/ServletContextAwareBean.java index d9f0109ca0af..6c6c95180356 100644 --- a/spring-test/src/test/java/org/springframework/test/context/web/ServletContextAwareBean.java +++ b/spring-test/src/test/java/org/springframework/test/context/web/ServletContextAwareBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2025 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. @@ -28,11 +28,15 @@ */ public class ServletContextAwareBean implements ServletContextAware { - protected ServletContext servletContext; + private ServletContext servletContext; @Override public void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; } + public ServletContext getServletContext() { + return this.servletContext; + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/web/ServletContextAwareBeanWacTests.java b/spring-test/src/test/java/org/springframework/test/context/web/ServletContextAwareBeanWacTests.java index d4eb31e33bf3..aa9059d77878 100644 --- a/spring-test/src/test/java/org/springframework/test/context/web/ServletContextAwareBeanWacTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/web/ServletContextAwareBeanWacTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2025 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,9 +16,13 @@ package org.springframework.test.context.web; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.testkit.engine.EngineTestKit; -import static org.springframework.test.context.junit4.JUnitTestingUtils.runTestsAndAssertCounters; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; /** * Introduced to investigate claims in SPR-11145. @@ -28,11 +32,16 @@ * @author Sam Brannen * @since 4.0.2 */ -public class ServletContextAwareBeanWacTests { +@ExtendWith(SpringExtension.class) +class ServletContextAwareBeanWacTests { @Test - public void ensureServletContextAwareBeanIsProcessedProperlyWhenExecutingJUnitManually() throws Exception { - runTestsAndAssertCounters(BasicAnnotationConfigWacTests.class, 3, 0, 3, 0, 0); + void ensureServletContextAwareBeanIsProcessedProperlyWhenExecutingJUnitManually() { + EngineTestKit.engine("junit-jupiter") + .selectors(selectClass(BasicAnnotationConfigWacTests.class)) + .execute() + .testEvents() + .assertStatistics(stats -> stats.started(3).succeeded(3).failed(0)); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/web/WebTestConfiguration.java b/spring-test/src/test/java/org/springframework/test/context/web/WebTestConfiguration.java index 1aab367a5891..d8dc4b3bdf39 100644 --- a/spring-test/src/test/java/org/springframework/test/context/web/WebTestConfiguration.java +++ b/spring-test/src/test/java/org/springframework/test/context/web/WebTestConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2025 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. @@ -36,11 +36,12 @@ public @interface WebTestConfiguration { } -@Configuration +@Configuration(proxyBeanMethods = false) class FooConfig { @Bean public String foo() { return "enigma"; } + } diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/spr6128/AutowiredQualifierTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/AutowiredQualifierTests-context.xml similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/spr6128/AutowiredQualifierTests-context.xml rename to spring-test/src/test/resources/org/springframework/test/context/AutowiredQualifierTests-context.xml diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/config/CoreContextConfigurationAppCtxTests-context.xml similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml rename to spring-test/src/test/resources/org/springframework/test/context/config/CoreContextConfigurationAppCtxTests-context.xml diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context1.xml b/spring-test/src/test/resources/org/springframework/test/context/config/MultipleResourcesContextConfigurationAppCtxTests-context1.xml similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context1.xml rename to spring-test/src/test/resources/org/springframework/test/context/config/MultipleResourcesContextConfigurationAppCtxTests-context1.xml diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context2.xml b/spring-test/src/test/resources/org/springframework/test/context/config/MultipleResourcesContextConfigurationAppCtxTests-context2.xml similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context2.xml rename to spring-test/src/test/resources/org/springframework/test/context/config/MultipleResourcesContextConfigurationAppCtxTests-context2.xml diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context3.xml b/spring-test/src/test/resources/org/springframework/test/context/config/MultipleResourcesContextConfigurationAppCtxTests-context3.xml similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context3.xml rename to spring-test/src/test/resources/org/springframework/test/context/config/MultipleResourcesContextConfigurationAppCtxTests-context3.xml diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/hybrid/HybridContextLoaderTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/hybrid/HybridContextLoaderTests-context.xml similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/hybrid/HybridContextLoaderTests-context.xml rename to spring-test/src/test/resources/org/springframework/test/context/hybrid/HybridContextLoaderTests-context.xml diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/spr3896/BeanOverridingDefaultLocationsInheritedTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/inheritance/BeanOverridingDefaultLocationsInheritedTests-context.xml similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/spr3896/BeanOverridingDefaultLocationsInheritedTests-context.xml rename to spring-test/src/test/resources/org/springframework/test/context/inheritance/BeanOverridingDefaultLocationsInheritedTests-context.xml diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/spr3896/DefaultLocationsBaseTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/inheritance/DefaultLocationsBaseTests-context.xml similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/spr3896/DefaultLocationsBaseTests-context.xml rename to spring-test/src/test/resources/org/springframework/test/context/inheritance/DefaultLocationsBaseTests-context.xml diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/spr3896/DefaultLocationsInheritedTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/inheritance/DefaultLocationsInheritedTests-context.xml similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/spr3896/DefaultLocationsInheritedTests-context.xml rename to spring-test/src/test/resources/org/springframework/test/context/inheritance/DefaultLocationsInheritedTests-context.xml diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/initializers/xml/MultipleInitializersXmlConfigTests-context.xml similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests-context.xml rename to spring-test/src/test/resources/org/springframework/test/context/initializers/xml/MultipleInitializersXmlConfigTests-context.xml diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/spr8849/datasource-config-with-auto-generated-db-name.xml b/spring-test/src/test/resources/org/springframework/test/context/jdbc/datasource-config-with-auto-generated-db-name.xml similarity index 93% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/spr8849/datasource-config-with-auto-generated-db-name.xml rename to spring-test/src/test/resources/org/springframework/test/context/jdbc/datasource-config-with-auto-generated-db-name.xml index 0ee91d260f78..5b30a9820d65 100644 --- a/spring-test/src/test/resources/org/springframework/test/context/junit4/spr8849/datasource-config-with-auto-generated-db-name.xml +++ b/spring-test/src/test/resources/org/springframework/test/context/jdbc/datasource-config-with-auto-generated-db-name.xml @@ -6,7 +6,7 @@ http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd"> - + diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/spr8849/datasource-config.xml b/spring-test/src/test/resources/org/springframework/test/context/jdbc/datasource-config.xml similarity index 93% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/spr8849/datasource-config.xml rename to spring-test/src/test/resources/org/springframework/test/context/jdbc/datasource-config.xml index b0c399df5334..6d685c03db04 100644 --- a/spring-test/src/test/resources/org/springframework/test/context/junit4/spr8849/datasource-config.xml +++ b/spring-test/src/test/resources/org/springframework/test/context/jdbc/datasource-config.xml @@ -6,7 +6,7 @@ http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd"> - + diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/spr8849/spr8849-schema.sql b/spring-test/src/test/resources/org/springframework/test/context/jdbc/enigma-schema.sql similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/spr8849/spr8849-schema.sql rename to spring-test/src/test/resources/org/springframework/test/context/jdbc/enigma-schema.sql diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests-context.xml index d014ce74d121..2314a9b90aea 100644 --- a/spring-test/src/test/resources/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests-context.xml +++ b/spring-test/src/test/resources/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests-context.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> - + diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/spr9799/Spr9799XmlConfigTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/junit4/spr9799/Spr9799XmlConfigTests-context.xml deleted file mode 100644 index 9eb8fced790a..000000000000 --- a/spring-test/src/test/resources/org/springframework/test/context/junit4/spr9799/Spr9799XmlConfigTests-context.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/orm/hibernate/HibernateSessionFlushingTests-context.xml similarity index 82% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests-context.xml rename to spring-test/src/test/resources/org/springframework/test/context/orm/hibernate/HibernateSessionFlushingTests-context.xml index 7ef4e6b9fad8..706386f998ec 100644 --- a/spring-test/src/test/resources/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests-context.xml +++ b/spring-test/src/test/resources/org/springframework/test/context/orm/hibernate/HibernateSessionFlushingTests-context.xml @@ -8,13 +8,13 @@ http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> - + - - + + - org/springframework/test/context/junit4/orm/domain/Person.hbm.xml - org/springframework/test/context/junit4/orm/domain/DriversLicense.hbm.xml + org/springframework/test/context/orm/hibernate/domain/Person.hbm.xml + org/springframework/test/context/orm/hibernate/domain/DriversLicense.hbm.xml diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/orm/db-schema.sql b/spring-test/src/test/resources/org/springframework/test/context/orm/hibernate/db-schema.sql similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/orm/db-schema.sql rename to spring-test/src/test/resources/org/springframework/test/context/orm/hibernate/db-schema.sql diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/orm/db-test-data.sql b/spring-test/src/test/resources/org/springframework/test/context/orm/hibernate/db-test-data.sql similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/orm/db-test-data.sql rename to spring-test/src/test/resources/org/springframework/test/context/orm/hibernate/db-test-data.sql diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/orm/domain/DriversLicense.hbm.xml b/spring-test/src/test/resources/org/springframework/test/context/orm/hibernate/domain/DriversLicense.hbm.xml similarity index 78% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/orm/domain/DriversLicense.hbm.xml rename to spring-test/src/test/resources/org/springframework/test/context/orm/hibernate/domain/DriversLicense.hbm.xml index 8f98d7d051e8..12fe95474833 100644 --- a/spring-test/src/test/resources/org/springframework/test/context/junit4/orm/domain/DriversLicense.hbm.xml +++ b/spring-test/src/test/resources/org/springframework/test/context/orm/hibernate/domain/DriversLicense.hbm.xml @@ -4,7 +4,7 @@ - + diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/orm/domain/Person.hbm.xml b/spring-test/src/test/resources/org/springframework/test/context/orm/hibernate/domain/Person.hbm.xml similarity index 77% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/orm/domain/Person.hbm.xml rename to spring-test/src/test/resources/org/springframework/test/context/orm/hibernate/domain/Person.hbm.xml index b0598cc2fe22..447ca5d4afd2 100644 --- a/spring-test/src/test/resources/org/springframework/test/context/junit4/orm/domain/Person.hbm.xml +++ b/spring-test/src/test/resources/org/springframework/test/context/orm/hibernate/domain/Person.hbm.xml @@ -4,12 +4,12 @@ - + - diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/profile/importresource/import.xml b/spring-test/src/test/resources/org/springframework/test/context/profile/importresource/import.xml similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/profile/importresource/import.xml rename to spring-test/src/test/resources/org/springframework/test/context/profile/importresource/import.xml diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/profile/xml/DefaultProfileXmlConfigTests-context.xml similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests-context.xml rename to spring-test/src/test/resources/org/springframework/test/context/profile/xml/DefaultProfileXmlConfigTests-context.xml diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/transactionalTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/transaction/transactionalTests-context.xml similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/context/junit4/transactionalTests-context.xml rename to spring-test/src/test/resources/org/springframework/test/context/transaction/transactionalTests-context.xml diff --git a/spring-test/src/test/resources/org/springframework/test/context/web/EnableWebMvcXmlConfigTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/web/EnableWebMvcXmlConfigTests-context.xml new file mode 100644 index 000000000000..7437f492b24c --- /dev/null +++ b/spring-test/src/test/resources/org/springframework/test/context/web/EnableWebMvcXmlConfigTests-context.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/spring-web/src/main/java/org/springframework/http/HttpEntity.java b/spring-web/src/main/java/org/springframework/http/HttpEntity.java index b3203e4e05de..c9359ba60b8f 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpEntity.java +++ b/spring-web/src/main/java/org/springframework/http/HttpEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 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. @@ -58,9 +58,10 @@ public class HttpEntity { /** - * The empty {@code HttpEntity}, with no body or headers. + * An {@code HttpEntity} instance with a {@code null} body and + * {@link HttpHeaders#EMPTY empty headers}. */ - public static final HttpEntity EMPTY = new HttpEntity<>(); + public static final HttpEntity EMPTY = new HttpEntity<>(HttpHeaders.EMPTY); private final HttpHeaders headers; diff --git a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java index 6d62a8ffbb34..bbcdcc87f847 100644 --- a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -75,6 +75,7 @@ public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequest private long readTimeout = -1; + /** * Create a new instance of the {@code HttpComponentsClientHttpRequestFactory} * with a default {@link HttpClient} based on system properties. @@ -248,9 +249,8 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO context = HttpClientContext.create(); } - // Request configuration not set in the context - if (!(context instanceof HttpClientContext clientContext && clientContext.getRequestConfig() != null) && - context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) { + // No custom request configuration was set + if (!hasCustomRequestConfig(context)) { RequestConfig config = null; // Use request configuration given by the user, when available if (httpRequest instanceof Configurable configurable) { @@ -269,6 +269,18 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO return new HttpComponentsClientHttpRequest(client, httpRequest, context); } + @SuppressWarnings("deprecation") // HttpClientContext.REQUEST_CONFIG + private static boolean hasCustomRequestConfig(HttpContext context) { + if (context instanceof HttpClientContext clientContext) { + // Prior to 5.4, the default config was set to RequestConfig.DEFAULT + // As of 5.4, it is set to null + RequestConfig requestConfig = clientContext.getRequestConfig(); + return requestConfig != null && !requestConfig.equals(RequestConfig.DEFAULT); + } + // Prior to 5.4, the config was stored as an attribute + return context.getAttribute(HttpClientContext.REQUEST_CONFIG) != null; + } + /** * Create a default {@link RequestConfig} to use with the given client. @@ -360,7 +372,7 @@ protected void postProcessHttpRequest(ClassicHttpRequest request) { } /** - * Template methods that creates a {@link HttpContext} for the given HTTP method and URI. + * Template method that creates a {@link HttpContext} for the given HTTP method and URI. *

        The default implementation returns {@code null}. * @param httpMethod the HTTP method * @param uri the URI diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java index 89320c8656c9..85501ab31549 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -184,7 +184,6 @@ public void registerObjectMappersForType(Class clazz, Consumer getObjectMappersForType(Class clazz) { for (Map.Entry, Map> entry : getObjectMapperRegistrations().entrySet()) { if (entry.getKey().isAssignableFrom(clazz)) { diff --git a/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java b/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java index 67c4ecca9061..126c1e88aadb 100644 --- a/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -85,7 +85,8 @@ public ResponseEntity exchangeForEntity(HttpRequestValues values, Paramet return newRequest(values).retrieve().toEntity(bodyType); } - private RestClient.RequestBodySpec newRequest(HttpRequestValues values) { + @SuppressWarnings("unchecked") + private RestClient.RequestBodySpec newRequest(HttpRequestValues values) { HttpMethod httpMethod = values.getHttpMethod(); Assert.notNull(httpMethod, "HttpMethod is required"); @@ -123,8 +124,14 @@ else if (values.getUriTemplate() != null) { bodySpec.attributes(attributes -> attributes.putAll(values.getAttributes())); - if (values.getBodyValue() != null) { - bodySpec.body(values.getBodyValue()); + B body = (B) values.getBodyValue(); + if (body != null) { + if (values.getBodyValueType() != null) { + bodySpec.body(body, (ParameterizedTypeReference) values.getBodyValueType()); + } + else { + bodySpec.body(body); + } } return bodySpec; diff --git a/spring-web/src/main/java/org/springframework/web/client/support/RestTemplateAdapter.java b/spring-web/src/main/java/org/springframework/web/client/support/RestTemplateAdapter.java index 364caa6be326..40a1a6f7a5c6 100644 --- a/spring-web/src/main/java/org/springframework/web/client/support/RestTemplateAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/client/support/RestTemplateAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 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. @@ -86,7 +86,7 @@ public ResponseEntity exchangeForEntity(HttpRequestValues values, Paramet return this.restTemplate.exchange(newRequest(values), bodyType); } - private RequestEntity newRequest(HttpRequestValues values) { + private RequestEntity newRequest(HttpRequestValues values) { HttpMethod httpMethod = values.getHttpMethod(); Assert.notNull(httpMethod, "HttpMethod is required"); @@ -120,11 +120,16 @@ else if (values.getUriTemplate() != null) { builder.header(HttpHeaders.COOKIE, String.join("; ", cookies)); } - if (values.getBodyValue() != null) { - return builder.body(values.getBodyValue()); + Object body = values.getBodyValue(); + if (body == null) { + return builder.build(); } - return builder.build(); + if (values.getBodyValueType() != null) { + return builder.body(body, values.getBodyValueType().getType()); + } + + return builder.body(body); } diff --git a/spring-web/src/main/java/org/springframework/web/context/ConfigurableWebEnvironment.java b/spring-web/src/main/java/org/springframework/web/context/ConfigurableWebEnvironment.java index d077036403a7..fb7ea08ec37e 100644 --- a/spring-web/src/main/java/org/springframework/web/context/ConfigurableWebEnvironment.java +++ b/spring-web/src/main/java/org/springframework/web/context/ConfigurableWebEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2025 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. @@ -39,7 +39,7 @@ public interface ConfigurableWebEnvironment extends ConfigurableEnvironment { * org.springframework.core.env.PropertySource.StubPropertySource stub property source} * instances acting as placeholders with real servlet context/config property sources * using the given parameters. - * @param servletContext the {@link ServletContext} (may not be {@code null}) + * @param servletContext the {@link ServletContext} ({@code null} if not available) * @param servletConfig the {@link ServletConfig} ({@code null} if not available) * @see org.springframework.web.context.support.WebApplicationContextUtils#initServletPropertySources( * org.springframework.core.env.MutablePropertySources, ServletContext, ServletConfig) diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpRequestValues.java b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpRequestValues.java index dfed3ab366cb..10bcb69b6beb 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpRequestValues.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpRequestValues.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -75,6 +76,9 @@ public class HttpRequestValues { @Nullable private final Object bodyValue; + @Nullable + private ParameterizedTypeReference bodyValueType; + /** * Construct {@link HttpRequestValues}. @@ -177,6 +181,15 @@ public Object getBodyValue() { return this.bodyValue; } + /** + * Return the type for the {@linkplain #getBodyValue() body value}. + * @since 6.2.7 + */ + @Nullable + public ParameterizedTypeReference getBodyValueType() { + return this.bodyValueType; + } + public static Builder builder() { return new Builder(); @@ -253,6 +266,9 @@ public static class Builder implements Metadata { @Nullable private Object bodyValue; + @Nullable + private ParameterizedTypeReference bodyValueType; + /** * Set the HTTP method for the request. */ @@ -389,6 +405,15 @@ public void setBodyValue(@Nullable Object bodyValue) { this.bodyValue = bodyValue; } + /** + * Variant of {@link #setBodyValue(Object)} with the body type. + * @since 6.2.7 + */ + public void setBodyValue(@Nullable Object bodyValue, @Nullable ParameterizedTypeReference valueType) { + setBodyValue(bodyValue); + this.bodyValueType = valueType; + } + // Implementation of {@link Metadata} methods @@ -465,9 +490,14 @@ else if (uri != null) { Map attributes = (this.attributes != null ? new HashMap<>(this.attributes) : Collections.emptyMap()); - return createRequestValues( + HttpRequestValues requestValues = createRequestValues( this.httpMethod, uri, uriBuilderFactory, uriTemplate, uriVars, headers, cookies, attributes, bodyValue); + + // In 6.2.x only, temporarily work around protected methods + requestValues.bodyValueType = this.bodyValueType; + + return requestValues; } protected boolean hasParts() { diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/RequestBodyArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/service/invoker/RequestBodyArgumentResolver.java index a84ef532acde..2aa8d5f58921 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/RequestBodyArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/RequestBodyArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -83,15 +83,16 @@ public boolean resolve( if (this.reactiveAdapterRegistry != null) { ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(parameter.getParameterType()); if (adapter != null) { - MethodParameter nestedParameter = parameter.nested(); + MethodParameter nestedParam = parameter.nested(); String message = "Async type for @RequestBody should produce value(s)"; Assert.isTrue(!adapter.isNoValue(), message); - Assert.isTrue(nestedParameter.getNestedParameterType() != Void.class, message); + Assert.isTrue(nestedParam.getNestedParameterType() != Void.class, message); - if (requestValues instanceof ReactiveHttpRequestValues.Builder reactiveRequestValues) { - reactiveRequestValues.setBodyPublisher( - adapter.toPublisher(argument), asParameterizedTypeRef(nestedParameter)); + if (requestValues instanceof ReactiveHttpRequestValues.Builder rrv) { + rrv.setBodyPublisher( + adapter.toPublisher(argument), + ParameterizedTypeReference.forType(nestedParam.getNestedGenericParameterType())); } else { throw new IllegalStateException( @@ -103,12 +104,8 @@ public boolean resolve( } // Not a reactive type - requestValues.setBodyValue(argument); + requestValues.setBodyValue(argument, ParameterizedTypeReference.forType(parameter.getGenericParameterType())); return true; } - private static ParameterizedTypeReference asParameterizedTypeRef(MethodParameter nestedParam) { - return ParameterizedTypeReference.forType(nestedParam.getNestedGenericParameterType()); - } - } diff --git a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java index 3af171ebc967..284bc2163bdb 100644 --- a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java +++ b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -451,11 +451,10 @@ private MultiValueMap expandQueryParams(UriTemplateVariables var UriTemplateVariables queryVariables = new QueryUriTemplateVariables(variables); this.queryParams.forEach((key, values) -> { String name = expandUriComponent(key, queryVariables, this.variableEncoder); - List expandedValues = new ArrayList<>(values.size()); + List expandedValues = result.computeIfAbsent(name, k -> new ArrayList<>(values.size())); for (String value : values) { expandedValues.add(expandUriComponent(value, queryVariables, this.variableEncoder)); } - result.put(name, expandedValues); }); return CollectionUtils.unmodifiableMultiValueMap(result); } diff --git a/spring-web/src/test/java/org/springframework/http/HttpEntityTests.java b/spring-web/src/test/java/org/springframework/http/HttpEntityTests.java index c6a75829107f..b79de1b9dd2f 100644 --- a/spring-web/src/test/java/org/springframework/http/HttpEntityTests.java +++ b/spring-web/src/test/java/org/springframework/http/HttpEntityTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -26,7 +26,10 @@ import static org.assertj.core.api.Assertions.assertThat; /** + * Unit tests for {@link HttpEntity}. + * * @author Arjen Poutsma + * @author Yanming Zhou */ class HttpEntityTests { @@ -34,6 +37,7 @@ class HttpEntityTests { void noHeaders() { String body = "foo"; HttpEntity entity = new HttpEntity<>(body); + assertThat(entity.getBody()).isSameAs(body); assertThat(entity.getHeaders()).isEmpty(); } @@ -44,6 +48,7 @@ void httpHeaders() { headers.setContentType(MediaType.TEXT_PLAIN); String body = "foo"; HttpEntity entity = new HttpEntity<>(body, headers); + assertThat(entity.getBody()).isEqualTo(body); assertThat(entity.getHeaders().getContentType()).isEqualTo(MediaType.TEXT_PLAIN); assertThat(entity.getHeaders().getFirst("Content-Type")).isEqualTo("text/plain"); @@ -55,6 +60,7 @@ void multiValueMap() { map.set("Content-Type", "text/plain"); String body = "foo"; HttpEntity entity = new HttpEntity<>(body, map); + assertThat(entity.getBody()).isEqualTo(body); assertThat(entity.getHeaders().getContentType()).isEqualTo(MediaType.TEXT_PLAIN); assertThat(entity.getHeaders().getFirst("Content-Type")).isEqualTo("text/plain"); @@ -123,4 +129,14 @@ void requestEntity() { assertThat(requestEntity2).isEqualTo(requestEntity); } + @Test // gh-34806 + void mutateEmptyInstanceHeaders() { + HttpHeaders headers = new HttpHeaders(HttpEntity.EMPTY.getHeaders()); + headers.add("Authorization", "Bearer some-token"); + + assertThat(HttpEntity.EMPTY.getHeaders()) + .as("Headers of HttpEntity.EMPTY should remain empty") + .isEmpty(); + } + } diff --git a/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java b/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java index 2f5e87f985c7..156404475a49 100644 --- a/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java @@ -22,7 +22,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.net.URI; +import java.util.LinkedHashSet; import java.util.Optional; +import java.util.Set; import java.util.function.BiFunction; import java.util.stream.Stream; @@ -267,6 +269,19 @@ void getWithIgnoredUriBuilderFactory(MockWebServer server, Service service) thro assertThat(this.anotherServer.getRequestCount()).isEqualTo(0); } + @ParameterizedAdapterTest // gh-34793 + void postSet(MockWebServer server, Service service) throws InterruptedException { + Set persons = new LinkedHashSet<>(); + persons.add(new Person("John")); + persons.add(new Person("Richard")); + service.postPersonSet(persons); + + RecordedRequest request = server.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()).isEqualTo("/persons"); + assertThat(request.getBody().readUtf8()).isEqualTo("[{\"name\":\"John\"},{\"name\":\"Richard\"}]"); + } + private static MockWebServer anotherServer() { MockWebServer server = new MockWebServer(); @@ -297,6 +312,9 @@ private interface Service { @PostExchange void postMultipart(MultipartFile file, @RequestPart String anotherPart); + @PostExchange(url = "/persons", contentType = MediaType.APPLICATION_JSON_VALUE) + void postPersonSet(@RequestBody Set set); + @PutExchange void putWithCookies(@CookieValue String firstCookie, @CookieValue String secondCookie); @@ -315,4 +333,19 @@ ResponseEntity getWithUriBuilderFactory(UriBuilderFactory uriBuilderFact ResponseEntity getWithIgnoredUriBuilderFactory(URI uri, UriBuilderFactory uriBuilderFactory); } + + static final class Person { + + private final String name; + + Person(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + } + } diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestBodyArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestBodyArgumentResolverTests.java index 3b82d00164a1..bab548d13d4b 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestBodyArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestBodyArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -54,6 +54,7 @@ void stringBody() { this.service.execute(body); assertThat(getBodyValue()).isEqualTo(body); + assertThat(getBodyValueType()).isEqualTo(new ParameterizedTypeReference() {}); assertThat(getPublisherBody()).isNull(); } @@ -173,6 +174,11 @@ private Object getBodyValue() { return getReactiveRequestValues().getBodyValue(); } + @Nullable + private ParameterizedTypeReference getBodyValueType() { + return getReactiveRequestValues().getBodyValueType(); + } + @Nullable private Publisher getPublisherBody() { return getReactiveRequestValues().getBodyPublisher(); diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java index f31c71bb1a80..695d48f49d0d 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java @@ -629,6 +629,17 @@ void parseBuildAndExpandHierarchical(ParserType parserType) { assertThat(uri.toString()).isEqualTo("ws://example.org:7777/path?q=1#foo"); } + @ParameterizedTest // gh-34783 + @EnumSource + void parseBuildAndExpandQueryParamWithSameName(ParserType parserType) { + UriComponents result = UriComponentsBuilder + .fromUriString("/?{pk1}={pv1}&{pk2}={pv2}", parserType) + .buildAndExpand("k1", "v1", "k1", "v2"); + + assertThat(result.getQuery()).isEqualTo("k1=v1&k1=v2"); + assertThat(result.getQueryParams()).containsExactly(Map.entry("k1", List.of("v1", "v2"))); + } + @ParameterizedTest @EnumSource void buildAndExpandOpaque(ParserType parserType) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/WebClientAdapter.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/WebClientAdapter.java index 03e85490bafa..9f947358f71a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/WebClientAdapter.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/WebClientAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -98,8 +98,8 @@ public Mono>> exchangeForEntityFlux(HttpRequestValues return newRequest(requestValues).retrieve().toEntityFlux(bodyType); } - @SuppressWarnings("ReactiveStreamsUnusedPublisher") - private WebClient.RequestBodySpec newRequest(HttpRequestValues values) { + @SuppressWarnings({"ReactiveStreamsUnusedPublisher", "unchecked"}) + private WebClient.RequestBodySpec newRequest(HttpRequestValues values) { HttpMethod httpMethod = values.getHttpMethod(); Assert.notNull(httpMethod, "HttpMethod is required"); @@ -130,12 +130,18 @@ else if (values.getUriTemplate() != null) { bodySpec.attributes(attributes -> attributes.putAll(values.getAttributes())); if (values.getBodyValue() != null) { - bodySpec.bodyValue(values.getBodyValue()); + if (values.getBodyValueType() != null) { + B body = (B) values.getBodyValue(); + bodySpec.bodyValue(body, (ParameterizedTypeReference) values.getBodyValueType()); + } + else { + bodySpec.bodyValue(values.getBodyValue()); + } } - else if (values instanceof ReactiveHttpRequestValues reactiveRequestValues) { - Publisher body = reactiveRequestValues.getBodyPublisher(); + else if (values instanceof ReactiveHttpRequestValues rhrv) { + Publisher body = rhrv.getBodyPublisher(); if (body != null) { - ParameterizedTypeReference elementType = reactiveRequestValues.getBodyPublisherElementType(); + ParameterizedTypeReference elementType = rhrv.getBodyPublisherElementType(); Assert.notNull(elementType, "Publisher body element type is required"); bodySpec.body(body, elementType); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/Fragment.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/Fragment.java index f835b3f6805e..446383eed9c8 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/Fragment.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/Fragment.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -91,9 +91,7 @@ public void mergeAttributes(Model model) { if (CollectionUtils.isEmpty(model.asMap())) { return; } - if (this.model == null) { - this.model = new LinkedHashMap<>(); - } + this.model = new LinkedHashMap<>(this.model != null ? this.model : Collections.emptyMap()); model.asMap().forEach((key, value) -> this.model.putIfAbsent(key, value)); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/WebSocketHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/WebSocketHandler.java index 0703dae330d4..ead8608fe815 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/WebSocketHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/WebSocketHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2025 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. @@ -23,65 +23,69 @@ import reactor.core.publisher.Mono; /** - * Handler for a WebSocket session. - * - *

        A server {@code WebSocketHandler} is mapped to requests with + * Handler for a WebSocket messages. You can use it as follows: + *

          + *
        • On the server side, {@code WebSocketHandler} is mapped to requests with * {@link org.springframework.web.reactive.handler.SimpleUrlHandlerMapping * SimpleUrlHandlerMapping} and * {@link org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter - * WebSocketHandlerAdapter}. A client {@code WebSocketHandler} is passed to the + * WebSocketHandlerAdapter}. + *
        • On the client side, {@code WebSocketHandler} is passed into the * {@link org.springframework.web.reactive.socket.client.WebSocketClient * WebSocketClient} execute method. + *
        * - *

        Use {@link WebSocketSession#receive() session.receive()} to compose on - * the inbound message stream, and {@link WebSocketSession#send(Publisher) - * session.send(publisher)} for the outbound message stream. Below is an - * example, combined flow to process inbound and to send outbound messages: + *

        {@link WebSocketSession#receive() session.receive()} handles inbound + * messages, while {@link WebSocketSession#send(Publisher) session.send} + * sends outbound messages. Below is an example of handling inbound messages + * and responding to every message: * *

        - * class ExampleHandler implements WebSocketHandler {
        - *
        - * 	@Override
        - * 	public Mono<Void> handle(WebSocketSession session) {
        - *
        - * 		Flux<WebSocketMessage> output = session.receive()
        - *			.doOnNext(message -> {
        - * 				// ...
        - * 			})
        - * 			.concatMap(message -> {
        - * 				// ...
        - * 			})
        - * 			.map(value -> session.textMessage("Echo " + value));
        - *
        - * 		return session.send(output);
        - * 	}
        - * }
        + *	class ExampleHandler implements WebSocketHandler {
        + *
        + *		@Override
        + *		public Mono<Void> handle(WebSocketSession session) {
        + *			Flux<WebSocketMessage> output = session.receive()
        + * 				.doOnNext(message -> {
        + * 					// Imperative calls without a return value:
        + * 					// perform access checks, log, validate, update metrics.
        + * 					// ...
        + * 				})
        + * 				.concatMap(message -> {
        + * 					// Async, non-blocking calls:
        + * 					// parse messages, call a database, make remote calls.
        + * 					// Return the same message, or a transformed value
        + * 					// ...
        + * 				});
        + * 			return session.send(output);
        + *		}
        + *	}
          * 
        * *

        If processing inbound and sending outbound messages are independent * streams, they can be joined together with the "zip" operator: * *

        - * class ExampleHandler implements WebSocketHandler {
        - *
        - * 	@Override
        - * 	public Mono<Void> handle(WebSocketSession session) {
        - *
        - * 		Mono<Void> input = session.receive()
        - *			.doOnNext(message -> {
        - * 				// ...
        - * 			})
        - * 			.concatMap(message -> {
        - * 				// ...
        - * 			})
        - * 			.then();
        - *
        - *		Flux<String> source = ... ;
        - * 		Mono<Void> output = session.send(source.map(session::textMessage));
        - *
        - * 		return Mono.zip(input, output).then();
        - * 	}
        - * }
        + *	class ExampleHandler implements WebSocketHandler {
        + *
        + *		@Override
        + *		public Mono<Void> handle(WebSocketSession session) {
        + *
        + *			Mono<Void> input = session.receive()
        + *				.doOnNext(message -> {
        + * 					// ...
        + * 				})
        + * 				.concatMap(message -> {
        + * 					// ...
        + * 				})
        + * 				.then();
        + *
        + *			Flux<String> source = ... ;
        + * 			Mono<Void> output = session.send(source.map(session::textMessage));
        + *
        + * 			return Mono.zip(input, output).then();
        + *		}
        + *	}
          * 
        * *

        A {@code WebSocketHandler} must compose the inbound and outbound streams diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/WebClientAdapterTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/WebClientAdapterTests.java index 5ee065af5abe..82120cb3b5ad 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/WebClientAdapterTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/WebClientAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -21,7 +21,9 @@ import java.net.URI; import java.time.Duration; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import okhttp3.mockwebserver.MockResponse; @@ -39,6 +41,7 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.multipart.MultipartFile; @@ -168,6 +171,22 @@ void multipart() throws InterruptedException { "Content-Type: text/plain;charset=UTF-8", "Content-Length: 5", "test2"); } + @Test // gh-34793 + void postSet() throws InterruptedException { + prepareResponse(response -> response.setResponseCode(201)); + + Set persons = new LinkedHashSet<>(); + persons.add(new Person("John")); + persons.add(new Person("Richard")); + + initService().postPersonSet(persons); + + RecordedRequest request = server.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()).isEqualTo("/persons"); + assertThat(request.getBody().readUtf8()).isEqualTo("[{\"name\":\"John\"},{\"name\":\"Richard\"}]"); + } + @Test void uriBuilderFactory() throws Exception { String ignoredResponseBody = "hello"; @@ -251,6 +270,9 @@ private interface Service { @PostExchange void postMultipart(MultipartFile file, @RequestPart String anotherPart); + @PostExchange("/persons") + void postPersonSet(@RequestBody Set set); + @GetExchange("/greeting") String getWithUriBuilderFactory(UriBuilderFactory uriBuilderFactory); @@ -263,4 +285,19 @@ String getWithUriBuilderFactory(UriBuilderFactory uriBuilderFactory, } + + static final class Person { + + private final String name; + + Person(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + } + } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/FragmentTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/FragmentTests.java new file mode 100644 index 000000000000..10edd3b5add3 --- /dev/null +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/FragmentTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2025 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.web.reactive.result.view; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.ui.ConcurrentModel; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link Fragment}. + * @author Rossen Stoyanchev + */ +public class FragmentTests { + + @Test + void mergeAttributes() { + Fragment fragment = Fragment.create("myView", Map.of("fruit", "apple")); + fragment.mergeAttributes(new ConcurrentModel("vegetable", "pepper")); + + assertThat(fragment.model()).containsExactly(Map.entry("fruit", "apple"), Map.entry("vegetable", "pepper")); + } + + @Test + void mergeAttributesCollision() { + Fragment fragment = Fragment.create("myView", Map.of("fruit", "apple")); + fragment.mergeAttributes(new ConcurrentModel("fruit", "orange")); + + assertThat(fragment.model()).containsExactly(Map.entry("fruit", "apple")); + } + +} diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index 791d70867892..577be682ceb3 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -15,8 +15,9 @@ - + + @@ -53,7 +54,6 @@ - @@ -114,8 +114,7 @@ - - +