diff --git a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java index 8307b5eecd..1764a054cd 100644 --- a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java @@ -395,6 +395,7 @@ public class MergedContextConfiguration implements Serializable { * {@code PropertySources}. * @since 6.1 * @see TestPropertySource#locations + * @see TestPropertySource#encoding * @see TestPropertySource#factory */ public List getPropertySourceDescriptors() { diff --git a/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java b/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java index a2ffa04723..8eaba4136e 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java @@ -145,6 +145,7 @@ public @interface TestPropertySource { * @see #inheritLocations * @see #value * @see #properties + * @see #encoding * @see #factory * @see org.springframework.core.env.PropertySource */ @@ -280,6 +281,14 @@ public @interface TestPropertySource { */ boolean inheritProperties() default true; + /** + * Specify the character encoding for the given {@linkplain #locations resources} + * — for example, "UTF-8". + *

If not specified, the default character encoding of the JVM will be used. + * @since 6.1 + */ + String encoding() default ""; + /** * Specify a custom {@link PropertySourceFactory}, if any. *

By default, a factory for standard resource files will be used which diff --git a/spring-test/src/main/java/org/springframework/test/context/support/MergedTestPropertySources.java b/spring-test/src/main/java/org/springframework/test/context/support/MergedTestPropertySources.java index 6c89dad7a4..ba76d8f191 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/MergedTestPropertySources.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/MergedTestPropertySources.java @@ -70,7 +70,9 @@ class MergedTestPropertySources { /** * Get the descriptors for resource locations of properties files. - * @see TestPropertySource#locations() + * @see TestPropertySource#locations + * @see TestPropertySource#encoding + * @see TestPropertySource#factory */ List getPropertySourceDescriptors() { return this.descriptors; diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java index 3d08ad0e73..388784fa39 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java @@ -126,22 +126,28 @@ class TestPropertySourceAttributes { TestContextResourceUtils.convertToClasspathResourcePaths(declaringClass, true, locations); Class factoryClass = (Class) mergedAnnotation.getClass("factory"); + String encoding = mergedAnnotation.getString("encoding"); + if (encoding.isBlank()) { + encoding = null; // default encoding will be inferred + } PropertySourceDescriptor descriptor = new PropertySourceDescriptor( - Arrays.asList(convertedLocations), false, null, factoryClass, null); - addPropertiesAndLocations(List.of(descriptor), properties, declaringClass, false); + List.of(convertedLocations), false, null, factoryClass, encoding); + addPropertiesAndLocations(List.of(descriptor), properties, declaringClass, encoding, false); } private void mergePropertiesAndLocationsFrom(TestPropertySourceAttributes attributes) { addPropertiesAndLocations(attributes.getPropertySourceDescriptors(), attributes.getProperties(), - attributes.getDeclaringClass(), true); + attributes.getDeclaringClass(), null, true); } private void addPropertiesAndLocations(List descriptors, String[] properties, - Class declaringClass, boolean prepend) { + Class declaringClass, String encoding, boolean prepend) { if (hasNoLocations(descriptors) && ObjectUtils.isEmpty(properties)) { String defaultPropertiesFile = detectDefaultPropertiesFile(declaringClass); - addAll(prepend, this.descriptors, List.of(new PropertySourceDescriptor(defaultPropertiesFile))); + PropertySourceDescriptor descriptor = new PropertySourceDescriptor( + List.of(defaultPropertiesFile), false, null, null, encoding); + addAll(prepend, this.descriptors, List.of(descriptor)); } else { addAll(prepend, this.descriptors, descriptors); diff --git a/spring-test/src/test/java/org/springframework/test/context/env/CustomEncodingTestPropertySourceTests.java b/spring-test/src/test/java/org/springframework/test/context/env/CustomEncodingTestPropertySourceTests.java new file mode 100644 index 0000000000..23ae8d6029 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/CustomEncodingTestPropertySourceTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.env; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link TestPropertySource @TestPropertySource} support + * with custom resource encoding. + * + * @author Sam Brannen + * @since 6.1 + */ +@SpringJUnitConfig +class CustomEncodingTestPropertySourceTests { + + @Nested + @TestPropertySource(locations = "test-ISO-8859-1.properties", encoding = "UTF-8" ) + @DirtiesContext + class IncorrectEncodingTests { + + @Test + void propertyIsAvailableInEnvironment(@Autowired Environment env) { + // The "é" characters in "Générales" get converted to U+FFFD : REPLACEMENT CHARACTER. + assertThat(env.getProperty("text")).isEqualTo("G\uFFFDn\uFFFDrales"); + } + } + + @Nested + @TestPropertySource(locations = "test-ISO-8859-1.properties", encoding = "ISO-8859-1" ) + @DirtiesContext + class ExplicitEncodingTests { + + @Test + void propertyIsAvailableInEnvironment(@Autowired Environment env) { + assertThat(env.getProperty("text")).isEqualTo("Générales"); + } + } + + + @Configuration + static class Config { + /* no user beans required for these tests */ + } + +} diff --git a/spring-test/src/test/resources/org/springframework/test/context/env/test-ISO-8859-1.properties b/spring-test/src/test/resources/org/springframework/test/context/env/test-ISO-8859-1.properties new file mode 100644 index 0000000000..a5b023bbfa --- /dev/null +++ b/spring-test/src/test/resources/org/springframework/test/context/env/test-ISO-8859-1.properties @@ -0,0 +1,2 @@ +# This file is saved using ISO-8859-1 encoding +text=Générales