diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/executing-sql.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/executing-sql.adoc index 62f432faf1d7..70c631bf8537 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/executing-sql.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/executing-sql.adoc @@ -122,6 +122,9 @@ classpath resource (for example, `"/org/example/schema.sql"`). A path that refer URL (for example, a path prefixed with `classpath:`, `file:`, `http:`) is loaded by using the specified resource protocol. +As of Spring Framework 6.2, paths may contain property placeholders (`${...}`) that will +be replaced by properties stored in the `Environment` of the test's `ApplicationContext`. + The following example shows how to use `@Sql` at the class level and at the method level within a JUnit Jupiter based integration test class: diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java index 5b4642637ddb..1838a732447d 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java @@ -115,6 +115,10 @@ * {@link org.springframework.util.ResourceUtils#CLASSPATH_URL_PREFIX classpath:}, * {@link org.springframework.util.ResourceUtils#FILE_URL_PREFIX file:}, * {@code http:}, etc.) will be loaded using the specified resource protocol. + *

As of Spring Framework 6.2, paths may contain property placeholders + * (${...}) that will be replaced by properties stored in the + * {@link org.springframework.core.env.Environment Environment} of the test's + * {@code ApplicationContext}. *

Default Script Detection

*

If no SQL scripts or {@link #statements} are specified, an attempt will * be made to detect a default script depending on where this @@ -131,6 +135,7 @@ * * @see #value * @see #statements + * @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String) */ @AliasFor("value") String[] scripts() default {}; diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java index 570eaec4dc43..7455ee94da77 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java @@ -308,8 +308,9 @@ else if (logger.isDebugEnabled()) { Method testMethod = (methodLevel ? testContext.getTestMethod() : null); String[] scripts = getScripts(sql, testContext.getTestClass(), testMethod, classLevel); + ApplicationContext applicationContext = testContext.getApplicationContext(); List scriptResources = TestContextResourceUtils.convertToResourceList( - testContext.getApplicationContext(), scripts); + applicationContext, applicationContext.getEnvironment(), scripts); for (String stmt : sql.statements()) { if (StringUtils.hasText(stmt)) { stmt = stmt.trim(); diff --git a/spring-test/src/main/java/org/springframework/test/context/util/TestContextResourceUtils.java b/spring-test/src/main/java/org/springframework/test/context/util/TestContextResourceUtils.java index ba809f2131a0..bd940007a278 100644 --- a/spring-test/src/main/java/org/springframework/test/context/util/TestContextResourceUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/util/TestContextResourceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.springframework.core.env.Environment; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePatternUtils; @@ -145,6 +146,28 @@ public static List convertToResourceList(ResourceLoader resourceLoader return stream(resourceLoader, paths).collect(Collectors.toCollection(ArrayList::new)); } + /** + * Convert the supplied paths to a list of {@link Resource} handles using the given + * {@link ResourceLoader} and {@link Environment}. + * @param resourceLoader the {@code ResourceLoader} to use to convert the paths + * @param environment the {@code Environment} to use to resolve property placeholders + * in the paths + * @param paths the paths to be converted + * @return a new, mutable list of resources + * @since 6.2 + * @see #convertToResources(ResourceLoader, String...) + * @see #convertToClasspathResourcePaths + * @see Environment#resolveRequiredPlaceholders(String) + */ + public static List convertToResourceList( + ResourceLoader resourceLoader, Environment environment, String... paths) { + + return Arrays.stream(paths) + .map(environment::resolveRequiredPlaceholders) + .map(resourceLoader::getResource) + .collect(Collectors.toCollection(ArrayList::new)); + } + private static Stream stream(ResourceLoader resourceLoader, String... paths) { return Arrays.stream(paths).map(resourceLoader::getResource); } diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/PropertyPlaceholderSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/PropertyPlaceholderSqlScriptsTests.java new file mode 100644 index 000000000000..8833de250c9a --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/PropertyPlaceholderSqlScriptsTests.java @@ -0,0 +1,61 @@ +/* + * 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.jdbc; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +/** + * Integration tests that verify support for property placeholders in SQL script locations. + * + * @author Sam Brannen + * @since 6.2 + */ +@ContextConfiguration(classes = PopulatedSchemaDatabaseConfig.class) +class PropertyPlaceholderSqlScriptsTests { + + private static final String SCRIPT_LOCATION = "classpath:org/springframework/test/context/jdbc/${vendor}/data.sql"; + + @Nested + @TestPropertySource(properties = "vendor = db1") + @DirtiesContext + class DatabaseOneTests extends AbstractTransactionalTests { + + @Test + @Sql(SCRIPT_LOCATION) + void placeholderIsResolvedInScriptLocation() { + assertUsers("Dilbert 1"); + } + } + + @Nested + @TestPropertySource(properties = "vendor = db2") + @DirtiesContext + class DatabaseTwoTests extends AbstractTransactionalTests { + + @Test + @Sql(SCRIPT_LOCATION) + void placeholderIsResolvedInScriptLocation() { + assertUsers("Dilbert 2"); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListenerTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListenerTests.java index 38dd0f77513e..b14cadb2f4c9 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListenerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListenerTests.java @@ -21,6 +21,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AnnotationConfigurationException; +import org.springframework.mock.env.MockEnvironment; import org.springframework.test.context.TestContext; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -84,6 +85,7 @@ void isolatedTxModeDeclaredWithoutTxMgr() throws Exception { ApplicationContext ctx = mock(); given(ctx.getResource(anyString())).willReturn(mock()); given(ctx.getAutowireCapableBeanFactory()).willReturn(mock()); + given(ctx.getEnvironment()).willReturn(new MockEnvironment()); Class clazz = IsolatedWithoutTxMgr.class; BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); @@ -98,6 +100,7 @@ void missingDataSourceAndTxMgr() throws Exception { ApplicationContext ctx = mock(); given(ctx.getResource(anyString())).willReturn(mock()); given(ctx.getAutowireCapableBeanFactory()).willReturn(mock()); + given(ctx.getEnvironment()).willReturn(new MockEnvironment()); Class clazz = MissingDataSourceAndTxMgr.class; BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); diff --git a/spring-test/src/test/resources/org/springframework/test/context/jdbc/db1/data.sql b/spring-test/src/test/resources/org/springframework/test/context/jdbc/db1/data.sql new file mode 100644 index 000000000000..f6e5532bd514 --- /dev/null +++ b/spring-test/src/test/resources/org/springframework/test/context/jdbc/db1/data.sql @@ -0,0 +1 @@ +INSERT INTO user VALUES('Dilbert 1'); \ No newline at end of file diff --git a/spring-test/src/test/resources/org/springframework/test/context/jdbc/db2/data.sql b/spring-test/src/test/resources/org/springframework/test/context/jdbc/db2/data.sql new file mode 100644 index 000000000000..4369cf8cf2a7 --- /dev/null +++ b/spring-test/src/test/resources/org/springframework/test/context/jdbc/db2/data.sql @@ -0,0 +1 @@ +INSERT INTO user VALUES('Dilbert 2'); \ No newline at end of file